From 89736a1256ae79f9a302556e355fabaaee658241 Mon Sep 17 00:00:00 2001 From: YuJack Date: Sun, 30 Jul 2023 15:43:18 +0800 Subject: [PATCH] Site updated: 2023-07-30 15:43:17 --- 2017/09/23/tappay-payment/index.html | 135 ++++-- 2017/09/23/todo-vue/index.html | 40 +- 2017/10/14/Slack-Bot/index.html | 111 +++-- 2017/10/17/google-hacking/index.html | 51 +- 2017/10/19/module-export/index.html | 38 +- 2017/10/20/secure-header/index.html | 172 +++++-- .../api-gateway-mapping-template/index.html | 79 ++- 2017/11/01/how-to-test/index.html | 90 +++- .../index.html | 68 ++- 2017/11/08/ngrok/index.html | 22 +- .../index.html | 39 +- 2017/12/11/express-static/index.html | 70 ++- 2018/04/21/apple-debug/index.html | 42 +- 2018/07/22/promise/index.html | 67 ++- 2018/09/05/cloudfont-setting/index.html | 21 +- 2019/01/08/ssh-tunnel/index.html | 63 ++- 2019/02/19/java-executor/index.html | 135 ++++-- 2019/04/24/javascript-this/index.html | 109 +++-- 2019/05/02/promise-2/index.html | 99 ++-- 2019/06/02/ajax-with-session/index.html | 106 ++-- 2019/09/04/hacker101-part1/index.html | 176 +++++-- 2019/09/06/hacker101-part2/index.html | 119 +++-- 2019/09/08/hacker101-part3/index.html | 65 ++- 2019/09/10/hacker101-part4/index.html | 84 +++- 2019/09/14/hacker101-part5/index.html | 109 ++++- 2019/09/30/http-smuggling/index.html | 193 ++++++-- 2019/10/20/hacker101-part6/index.html | 110 +++-- .../aws-cloudwatch-logs-insights/index.html | 87 +++- 2019/12/10/unit-test-express/index.html | 270 ++++++++--- .../index.html | 127 +++-- 2020/01/06/aws-certificate-manager/index.html | 35 +- .../aws-increase-disk-space-in-ec2/index.html | 39 +- .../index.html | 83 +++- .../index.html | 271 ++++++++--- 2020/02/10/javascript-accumulator/index.html | 53 +- 2020/02/17/ci-cd-jenkins/index.html | 195 ++++++-- 2020/02/24/java-oom/index.html | 114 ++++- 2020/03/02/ssl-pinning/index.html | 229 ++++++--- 2020/03/09/nodejs-zero-downtime/index.html | 132 +++-- .../index.html | 116 +++-- .../index.html" | 129 +++-- 2020/03/30/ec2-ipv6/index.html | 68 ++- 2020/04/06/sso-1/index.html | 132 +++-- 2020/04/13/sso-vs-oauth/index.html | 138 ++++-- 2020/04/20/sso-2/index.html | 133 ++++-- 2020/04/27/oauth-implement/index.html | 128 +++-- 2020/05/04/async-error-handle/index.html | 90 ++-- .../11/nodejs-zero-downtime-next/index.html | 92 ++-- 2020/05/18/docker-network/index.html | 74 ++- .../25/docker-swarm-load-balancing/index.html | 149 ++++-- .../docker-swarm-service-discovery/index.html | 104 +++- .../custom-dns-server-with-nodejs/index.html | 55 ++- 2020/06/26/ssh-broken-how-to-fix/index.html | 37 +- 2020/08/22/pug-with-vuejs/index.html | 81 +++- .../unit-test-best-practice-part-1/index.html | 160 +++++-- .../unit-test-best-practice-part-2/index.html | 122 +++-- .../unit-test-best-practice-part-3/index.html | 119 +++-- .../unit-test-best-practice-part-4/index.html | 128 +++-- .../unit-test-best-practice-part-5/index.html | 131 +++-- .../12/01/stackoverflow-experience/index.html | 258 +++++++--- 2020/12/12/tappay-payment-2/index.html | 54 ++- 2021/01/17/ci-cd-sonarqube/index.html | 200 +++++--- 2021/03/14/node-event-loop/index.html | 451 +++++++++++++----- 2021/04/18/go-practice-1/index.html | 92 ++-- 2021/06/20/new-to-learn-ruby/index.html | 231 ++++++--- 2021/07/04/rails-mechanism/index.html | 189 ++++++-- 2021/07/15/thread-model/index.html | 118 +++-- 2021/07/19/interview/index.html | 260 +++++++--- 2021/09/21/go-local-package/index.html | 50 +- 2021/10/02/rails-autoload-path/index.html | 108 +++-- 2021/11/24/helm-note/index.html | 53 +- 2021/11/25/upstream/index.html | 51 +- 2022/01/09/go-summary/index.html | 142 +++--- 2022/02/22/pulumi-tutor/index.html | 76 +-- 2022/03/13/pulumi-tutor-2/index.html | 39 +- 2022/10/04/go-concurrency/index.html | 20 +- .../28/water-ball-design-patterns/index.html | 49 +- 2023/07/18/how-minds-change/index.html | 50 +- archives/2017/09/index.html | 4 +- archives/2017/10/index.html | 4 +- archives/2017/11/index.html | 4 +- archives/2017/12/index.html | 4 +- archives/2017/index.html | 4 +- archives/2018/04/index.html | 4 +- archives/2018/07/index.html | 4 +- archives/2018/09/index.html | 4 +- archives/2018/index.html | 4 +- archives/2019/01/index.html | 4 +- archives/2019/02/index.html | 4 +- archives/2019/04/index.html | 4 +- archives/2019/05/index.html | 4 +- archives/2019/06/index.html | 4 +- archives/2019/09/index.html | 4 +- archives/2019/10/index.html | 4 +- archives/2019/11/index.html | 4 +- archives/2019/12/index.html | 4 +- archives/2019/index.html | 4 +- archives/2020/01/index.html | 4 +- archives/2020/02/index.html | 4 +- archives/2020/03/index.html | 4 +- archives/2020/04/index.html | 4 +- archives/2020/05/index.html | 4 +- archives/2020/06/index.html | 4 +- archives/2020/08/index.html | 4 +- archives/2020/09/index.html | 4 +- archives/2020/10/index.html | 4 +- archives/2020/12/index.html | 4 +- archives/2020/index.html | 4 +- archives/2021/01/index.html | 4 +- archives/2021/03/index.html | 4 +- archives/2021/04/index.html | 4 +- archives/2021/06/index.html | 4 +- archives/2021/07/index.html | 4 +- archives/2021/09/index.html | 4 +- archives/2021/10/index.html | 4 +- archives/2021/11/index.html | 4 +- archives/2021/index.html | 4 +- archives/2022/01/index.html | 4 +- archives/2022/02/index.html | 4 +- archives/2022/03/index.html | 4 +- archives/2022/10/index.html | 4 +- archives/2022/index.html | 4 +- archives/2023/06/index.html | 4 +- archives/2023/07/index.html | 6 +- archives/2023/index.html | 6 +- archives/index.html | 6 +- categories/AWS/index.html | 86 ++-- categories/Bot/index.html | 12 +- categories/DevOps/index.html | 96 ++-- categories/Development/index.html | 15 +- categories/Experience/index.html | 23 +- categories/GoLang/index.html | 33 +- categories/Google/index.html | 18 +- categories/Java/index.html | 23 +- categories/JavaScript/index.html | 71 ++- categories/Modeling/index.html | 9 +- categories/NodeJs/index.html | 62 ++- categories/Operating-System/index.html | 12 +- categories/Payment-Gateway/index.html | 24 +- categories/Ruby/index.html | 23 +- categories/Security/index.html | 171 +++++-- categories/Test/index.html | 87 ++-- categories/Vue/index.html | 27 +- categories/debug/index.html | 16 +- categories/index.html | 78 +-- categories/tool/index.html | 17 +- .../index.html" | 34 +- category-sitemap.xml | 2 +- content.json | 2 +- index.html | 70 ++- page/2/index.html | 97 ++-- page/3/index.html | 104 ++-- page/4/index.html | 105 ++-- page/5/index.html | 111 +++-- page/6/index.html | 133 ++++-- page/7/index.html | 101 ++-- page/8/index.html | 90 ++-- post-sitemap.xml | 16 +- sitemap.xml | 2 +- tag-sitemap.xml | 30 +- tags/API-Gateway/index.html | 26 +- tags/Bot/index.html | 12 +- tags/CI-CD/index.html | 35 +- tags/CTF/index.html | 93 ++-- tags/Chat-Bot/index.html | 12 +- tags/CloudWatch/index.html | 18 +- tags/Cloudfront/index.html | 16 +- tags/DevOps/index.html | 69 ++- tags/Development/index.html | 31 +- tags/Executor/index.html | 11 +- tags/GIL/index.html | 14 +- tags/HTTPS/index.html | 14 +- tags/Java/index.html | 28 +- tags/Javascript/index.html | 82 +++- tags/OAuth/index.html | 27 +- tags/OOA/index.html | 9 +- tags/OOD/index.html | 9 +- tags/OOP/index.html | 9 +- tags/Payment-Gateway/index.html | 24 +- tags/SSL/index.html | 16 +- tags/SSO/index.html | 43 +- tags/Slack/index.html | 12 +- tags/Sonarqube/index.html | 13 +- tags/Stack-Overflow/index.html | 15 +- tags/TapPay/index.html | 24 +- tags/Test/index.html | 53 +- tags/TheadPoolExecutor/index.html | 11 +- tags/Thread-Pool/index.html | 11 +- tags/acm/index.html | 15 +- tags/apple-pay/index.html | 16 +- tags/architecture/index.html | 12 +- tags/async/index.html | 30 +- tags/await/index.html | 30 +- tags/aws/index.html | 86 ++-- tags/browser/index.html | 15 +- tags/callback/index.html | 20 +- tags/certificate/index.html | 15 +- tags/concurrency/index.html | 10 +- tags/cookie/index.html | 22 +- tags/cors/index.html | 10 +- tags/credential/index.html | 10 +- tags/debug/index.html | 16 +- tags/design-patterns/index.html | 9 +- tags/disk/index.html | 20 +- tags/dns/index.html | 18 +- tags/docker/index.html | 28 +- tags/download-file/index.html | 18 +- tags/ec2/index.html | 27 +- tags/ecma6/index.html | 15 +- tags/event-loop/index.html | 26 +- tags/experience/index.html | 23 +- tags/express/index.html | 23 +- tags/golang/index.html | 33 +- tags/google-hacking/index.html | 18 +- tags/header/index.html | 26 +- tags/helm/index.html | 13 +- tags/iOS/index.html | 16 +- tags/index.html | 78 +-- tags/interview/index.html | 12 +- tags/ip/index.html | 12 +- tags/ipv6/index.html | 11 +- tags/jenkins/index.html | 26 +- tags/k8s/index.html | 13 +- tags/lambda/index.html | 18 +- tags/localhost/index.html | 17 +- tags/mocha/index.html | 20 +- tags/mount/index.html | 16 +- tags/network/index.html | 37 +- tags/nginx/index.html | 12 +- tags/ngrok/index.html | 17 +- tags/node-js/index.html | 15 +- tags/nodejs/index.html | 99 ++-- tags/nodejs/page/2/index.html | 11 +- tags/npm/index.html | 17 +- tags/operating-system/index.html | 12 +- tags/package/index.html | 13 +- tags/process/index.html | 12 +- tags/promise/index.html | 30 +- tags/pug/index.html | 15 +- tags/pulumi/index.html | 15 +- tags/puma/index.html | 14 +- tags/query-string/index.html | 16 +- tags/rails/index.html | 19 +- tags/refactor/index.html | 16 +- tags/ruby/index.html | 23 +- tags/safari/index.html | 16 +- tags/search/index.html | 18 +- tags/security-header/index.html | 14 +- tags/security/index.html | 63 ++- tags/session/index.html | 10 +- tags/sinon/index.html | 22 +- tags/ssh-tunnel/index.html | 15 +- tags/ssh/index.html | 27 +- tags/test-double/index.html | 32 +- tags/this/index.html | 16 +- tags/thread-model/index.html | 12 +- tags/thread/index.html | 12 +- tags/todo-list/index.html | 16 +- tags/types/index.html | 17 +- tags/unit-test/index.html | 71 ++- tags/upload-file/index.html | 18 +- tags/vue-router/index.html | 16 +- tags/vue/index.html | 27 +- tags/vuex/index.html | 16 +- tags/w3HexSchool/index.html | 105 ++-- tags/w3HexSchool/page/2/index.html | 103 ++-- tags/w3HexSchool/page/3/index.html | 69 ++- tags/x-forwarded-for/index.html | 12 +- .../index.html" | 34 +- 269 files changed, 9453 insertions(+), 4004 deletions(-) diff --git a/2017/09/23/tappay-payment/index.html b/2017/09/23/tappay-payment/index.html index cf5d75334..8e3a0d764 100644 --- a/2017/09/23/tappay-payment/index.html +++ b/2017/09/23/tappay-payment/index.html @@ -19,12 +19,12 @@ - + - + @@ -175,13 +175,18 @@

-

[Update 2020-12-12] TapPay Web SDK 串接 - @types/tpdirect 介紹

-

這篇文章主要是說明如何使用 TapPay 這個服務
TapPay 是一家金流廠商,主要都是做線上金流,詳細就不多說
有興趣想要詳細了解可以去參考官網 https://www.tappaysdk.com

-

最近剛好被派去串接 TapPay 的服務,就順便把整個流程給記錄下來了
這邊會以 Web 服務為主去做範例,完整程式碼,請參考最下面

- -

環境設置

    -
  1. TapPay Portal 申請

    -

    要拿到以下的值才有辦法作後續的付款

    +

    <span style="color: green">[Update 2020-12-12]</span> TapPay Web SDK 串接 - @types/tpdirect 介紹

    +

    這篇文章主要是說明如何使用 TapPay 這個服務 +TapPay 是一家金流廠商,主要都是做線上金流,詳細就不多說 +有興趣想要詳細了解可以去參考官網 https://www.tappaysdk.com

    +

    最近剛好被派去串接 TapPay 的服務,就順便把整個流程給記錄下來了 +這邊會以 Web 服務為主去做範例,完整程式碼,請參考最下面

    +

    <!--more-->

    +

    環境設置

    +
      +
    1. +

      TapPay Portal 申請

      +

      要拿到以下的值才有辦法作後續的付款

      • App Key (應用程式頁面)
      • App ID (應用程式頁面)
      • @@ -189,24 +194,38 @@

        環境設置

        1. Merchant ID (商家管理頁面)
    2. -
    3. 程式部分

      +
    4. +

      程式部分

      • 前端: HTML + Javascript + CSS
      • 後端: nodejs (v6)
    5. -
    6. 網域部分

      +
    7. +

      網域部分

        -
      • 設置 /etc/hosts
        這邊要特別注意,要去 /etc/hots 底下設置
        跟在 TapPay Portal 所建立的 domain 一樣
        才有辦法 Get Prim,否則會一直出現 CORS 的問題
        待會在細部流程的時候會做介紹
      • +
      • 設置 /etc/hosts +這邊要特別注意,要去 /etc/hots 底下設置 +跟在 TapPay Portal 所建立的 domain 一樣 +才有辦法 Get Prim,否則會一直出現 CORS 的問題 +待會在細部流程的時候會做介紹
    8. -
    9. 測試卡號

      -

      測試卡號可以參考這裡
      https://docs.tappaysdk.com/tutorial/zh/reference.html#test-card
      card number 4242424242424242
      month 01
      year 23
      ccv 123

      +
    10. +

      測試卡號

      +

      測試卡號可以參考這裡 +https://docs.tappaysdk.com/tutorial/zh/reference.html#test-card +card number 4242424242424242 +month 01 +year 23 +ccv 123

    -

    流程介紹

    主要分成以下幾個步驟

    +

    流程介紹

    +

    主要分成以下幾個步驟

      -
    • 前端

      +
    • +

      前端

      1. 使用 TapPay SDK 設置好輸入卡號的表單
      2. 按下按鈕觸發 TapPay 的 GetPrime 方法
      3. @@ -214,7 +233,8 @@

        流程介紹

        主要分成以下幾個

      4. 把 Prime 送到後端
    • -
    • 後端

      +
    • +

      後端

      1. 拿到前端送來的 Prime
      2. 把 Prime 加上其他所需參數送往 TapPay Server
      3. @@ -222,42 +242,77 @@

        流程介紹

        主要分成以下幾個

    -

    程式撰寫 - 前端

    根據最新的 SDK 發佈的方法, 可以直接在一個 element 底下把卡號輸入表單塞進去

    -

    HTML

    HTML 分成兩個部分

    +

    程式撰寫 - 前端

    +

    根據最新的 SDK 發佈的方法, 可以直接在一個 element 底下把卡號輸入表單塞進去

    +

    HTML

    +

    HTML 分成兩個部分

    1. 建立好一個 div 準備等等被塞入輸入卡號表單
    2. 建立好 trigger button 來觸發 Get Prime 方法
    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div style="width: 480px; margin: 50px auto;">
    <label>CardView</label>

    <!-- 這是我們要塞表單的地方 -->
    <div id="cardview-container"></div>

    <!-- 這是我們要觸發 GetPrime 方法的地方 -->
    <button id="submit-button" onclick="onClick()">Get Prime</button>
    </div>
    -

    Javascript

    Javascript 分成三個部分

    +

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div style="width: 480px; margin: 50px auto;">
    <label>CardView</label>

    <!-- 這是我們要塞表單的地方 -->
    <div id="cardview-container"></div>

    <!-- 這是我們要觸發 GetPrime 方法的地方 -->
    <button id="submit-button" onclick="onClick()">Get Prime</button>
    </div>

    +

    Javascript

    +

    Javascript 分成三個部分

    1. 初始化金鑰
    2. 植入輸入卡號表單
    3. 觸發 getPrime 方法
    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 設置好等等 GetPrime 所需要的金鑰
    TPDirect.setupSDK(APP_ID, "APP_KEY", "sandbox")

    // 把 TapPay 內建輸入卡號的表單給植入到 div 中
    TPDirect.card.setup('#cardview-container')

    var submitButton = document.querySelector('#submit-button')

    function onClick() {
    // 讓 button click 之後觸發 getPrime 方法
    TPDirect.card.getPrime(function (result) {
    if (result.status !== 0) {
    console.err('getPrime 錯誤')
    return
    }
    var prime = result.card.prime
    alert('getPrime 成功: ' + prime)
    })
    }
    -

    沒錯!你沒看錯,不到 30 行
    但是,這邊要注意到一個地方,如果你 Get Prime 之後沒有任何反應
    打開開發者模式後卻看到了這個
    getPrime 錯誤
    題外話,如果並不使用 TPDirect.card.setup 版本的話
    而是自己實作整個流程,則會看到 CORS 的紅字

    -

    這個代表你開發的網域和你在 TapPay Portal 上面所填寫的網域是不一樣的
    這就是一開始在環境設置提到的 /etc/hosts 有關係

    -

    假設你未來可能要使用的網域是 example-tappay.yujack.com 的話
    請到 /etc/hosts localhost 下面加上一段

    -
    1
    2
    127.0.0.1 localhost
    127.0.0.1 example-tappay.yujack.com
    -

    然後回到網頁上把 URL 從
    http://localhost:8080/ 改成 http://example-tappay.yujack.com:8080/
    這樣 Get Prime 就會成功了!

    -

    不過要注意,如果你未來要用的網域是已經在用的話
    在 /etc/hosts 底下是上去是沒有用的
    所以切記用一個沒在用的網域做測試
    否則 .. 你只好直接部署上去測試了

    -

    程式撰寫 - 後端

    小弟我是習慣用 nodejs 撰寫後端伺服器
    所以這邊會以 nodejs 去做付款的動作
    前端 Get Prime 成功之後, 就要把這組 prime 送到後端了

    -

    建立 NodeJs server

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const express = require('express')
    const app = express()
    const bodyParser = require('body-parser')
    const https = require('https');
    const PORT = 8080

    app.use(bodyParser.json())
    app.use(bodyParser.urlencoded({
    extended: false
    }))
    app.use('/', express.static(__dirname + "/html")) //serve static content

    app.post('/pay-by-prime', (req, res, next) => {
    // 必須要把程式實作在這邊
    })

    app.listen(PORT, () => {
    console.log('Connet your webiste in the http://localhost:8080/');
    })
    -

    實作 Pay by Prime

    接下來要實作 pay-by-prime 的程式
    要加到 app.post(‘/pay-by-prime’) 裡面
    這裡有兩個參數要注意
    兩個都是在 TapPay Portal 上面申請帳號時會獲得的,程式如下

    +

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 設置好等等 GetPrime 所需要的金鑰
    TPDirect.setupSDK(APP_ID, "APP_KEY", "sandbox")

    // 把 TapPay 內建輸入卡號的表單給植入到 div 中
    TPDirect.card.setup('#cardview-container')

    var submitButton = document.querySelector('#submit-button')

    function onClick() {
    // 讓 button click 之後觸發 getPrime 方法
    TPDirect.card.getPrime(function (result) {
    if (result.status !== 0) {
    console.err('getPrime 錯誤')
    return
    }
    var prime = result.card.prime
    alert('getPrime 成功: ' + prime)
    })
    }

    +

    沒錯!你沒看錯,不到 30 行 +但是,這邊要注意到一個地方,如果你 Get Prime 之後沒有任何反應 +打開開發者模式後卻看到了這個 +getPrime 錯誤 +題外話,如果並不使用 TPDirect.card.setup 版本的話 +而是自己實作整個流程,則會看到 CORS 的紅字

    +

    這個代表你開發的網域和你在 TapPay Portal 上面所填寫的網域是不一樣的 +這就是一開始在環境設置提到的 /etc/hosts 有關係

    +

    假設你未來可能要使用的網域是 example-tappay.yujack.com 的話 +請到 /etc/hosts localhost 下面加上一段

    +

    1
    2
    127.0.0.1 localhost
    127.0.0.1 example-tappay.yujack.com

    +

    然後回到網頁上把 URL 從 +http://localhost:8080/ 改成 http://example-tappay.yujack.com:8080/ +這樣 Get Prime 就會成功了!

    +

    不過要注意,如果你未來要用的網域是已經在用的話 +在 /etc/hosts 底下是上去是沒有用的 +所以切記用一個沒在用的網域做測試 +否則 .. 你只好直接部署上去測試了

    +

    程式撰寫 - 後端

    +

    小弟我是習慣用 nodejs 撰寫後端伺服器 +所以這邊會以 nodejs 去做付款的動作 +前端 Get Prime 成功之後, 就要把這組 prime 送到後端了

    +

    建立 NodeJs server

    +

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const express = require('express')
    const app = express()
    const bodyParser = require('body-parser')
    const https = require('https');
    const PORT = 8080

    app.use(bodyParser.json())
    app.use(bodyParser.urlencoded({
    extended: false
    }))
    app.use('/', express.static(__dirname + "/html")) //serve static content

    app.post('/pay-by-prime', (req, res, next) => {
    // 必須要把程式實作在這邊
    })

    app.listen(PORT, () => {
    console.log('Connet your webiste in the http://localhost:8080/');
    })

    +

    實作 Pay by Prime

    +

    接下來要實作 pay-by-prime 的程式 +要加到 app.post('/pay-by-prime') 裡面 +這裡有兩個參數要注意 +兩個都是在 TapPay Portal 上面申請帳號時會獲得的,程式如下

    1. Partner Key (帳號資訊頁面)
    2. Merchant ID (商家管理頁面)
    -

    另外就是 headers 裡面要特別帶 x-api-key 進去
    否則會收到 access deny 的 response

    -

    可以參考 https://docs.tappaysdk.com/tutorial/zh/back.html#pay-by-prime-api
    所需要帶的參數和 headers

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    const post_data = {
    // prime from front-end
    "prime": req.body.prime,
    "partner_key": "PARTNER_KEY",
    "merchant_id": "MERCHANT_ID",
    "amount": 1,
    "currency": "TWD",
    "details": "An apple and a pen.",
    "cardholder": {
    "phone_number": "+886923456789",
    "name": "yujack",
    "email": "example@gmail.com"
    },
    "instalment": 0,
    "remember": false
    }

    const post_options = {
    host: 'sandbox.tappaysdk.com',
    port: 443,
    path: '/tpc/payment/pay-by-prime',
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    // 這個參數必須要帶上去,否則不會過
    'x-api-key': 'PARTNER_KEY'
    }
    }

    const post_req = https.request(post_options, function(response) {
    response.setEncoding('utf8');
    response.on('data', function (body) {
    return res.json({
    result: JSON.parse(body)
    })
    });
    });

    post_req.write(JSON.stringify(post_data));
    post_req.end();
    -

    實作完成後,開啟 nodejs server
    然後打上測試卡後就可以完成付款了!
    打完收工!下班去!

    -

    前端補正

    記得前端要補上把 prime 帶上來的程式

    1
    2
    3
    $.post('/pay-by-prime', {prime: prime}, function(data) {
    alert('付款成功' + JSON.stringify(data))
    })

    -

    完整程式碼

    資料夾結構

    1
    2
    3
    4
    5
    |
    |--- app.js
    |
    |----html
    | |---index.html
    -

    前端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script text="text/javascript" src="https://js.tappaysdk.com/tpdirect/v2_2_1"></script>
    <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
    <title>Connect payment with TapPay</title>
    </head>

    <body>
    <div style="width: 480px; margin: 50px auto;">
    <label>CardView</label>
    <div id="cardview-container"></div>
    <button id="submit-button" onclick="onClick()">Get Prime</button>
    <pre id="result1"></pre>
    <pre id="result2"></pre>
    </div>
    <script>
    TPDirect.setupSDK(APP_ID, 'APP_KEY', 'sandbox')
    TPDirect.card.setup('#cardview-container')

    var submitButton = document.querySelector('#submit-button')
    var cardViewContainer = document.querySelector('#cardview-container')


    function onClick() {
    TPDirect.card.getPrime(function (result) {
    if (result.status !== 0) {
    console.log('getPrime 錯誤')
    return
    }
    alert('getPrime 成功')
    var prime = result.card.prime
    document.querySelector('#result1').innerHTML = JSON.stringify(result, null, 4)

    $.post('/pay-by-prime', {prime: prime}, function(data) {
    alert('付款成功')
    document.querySelector('#result2').innerHTML = JSON.stringify(data, null, 4)
    })
    })
    }
    </script>
    </body>
    </html>
    -

    後端

    記得先執行以下 command

    1
    npm install body-parser express

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    const express = require('express')
    const app = express()
    const bodyParser = require('body-parser')
    const https = require('https');
    const PORT = 8080

    app.use(bodyParser.json())
    app.use(bodyParser.urlencoded({
    extended: false
    }))
    app.use('/', express.static(__dirname + "/html")) //serve static content

    app.post('/pay-by-prime', (req, res, next) => {
    const post_data = {
    "prime": req.body.prime,
    "partner_key": "PARTNER_KEY",
    "merchant_id": "MERCHANT_ID",
    "amount": 1,
    "currency": "TWD",
    "details": "An apple and a pen.",
    "cardholder": {
    "phone_number": "+886923456789",
    "name": "jack",
    "email": "example@gmail.com"
    },
    "remember": false
    }

    const post_options = {
    host: 'sandbox.tappaysdk.com',
    port: 443,
    path: '/tpc/payment/pay-by-prime',
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'PARTNER_KEY'
    }
    }

    const post_req = https.request(post_options, function(response) {
    response.setEncoding('utf8');
    response.on('data', function (body) {
    return res.json({
    result: JSON.parse(body)
    })
    });
    });

    post_req.write(JSON.stringify(post_data));
    post_req.end();

    })

    app.listen(PORT, () => {
    console.log('Connet your webiste in the http://localhost:8080/');
    })
    +

    另外就是 headers 裡面要特別帶 x-api-key 進去 +否則會收到 access deny 的 response

    +

    可以參考 https://docs.tappaysdk.com/tutorial/zh/back.html#pay-by-prime-api +所需要帶的參數和 headers

    +

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    const post_data = {
    // prime from front-end
    "prime": req.body.prime,
    "partner_key": "PARTNER_KEY",
    "merchant_id": "MERCHANT_ID",
    "amount": 1,
    "currency": "TWD",
    "details": "An apple and a pen.",
    "cardholder": {
    "phone_number": "+886923456789",
    "name": "yujack",
    "email": "example@gmail.com"
    },
    "instalment": 0,
    "remember": false
    }

    const post_options = {
    host: 'sandbox.tappaysdk.com',
    port: 443,
    path: '/tpc/payment/pay-by-prime',
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    // 這個參數必須要帶上去,否則不會過
    'x-api-key': 'PARTNER_KEY'
    }
    }

    const post_req = https.request(post_options, function(response) {
    response.setEncoding('utf8');
    response.on('data', function (body) {
    return res.json({
    result: JSON.parse(body)
    })
    });
    });

    post_req.write(JSON.stringify(post_data));
    post_req.end();

    +

    實作完成後,開啟 nodejs server +然後打上測試卡後就可以完成付款了! +打完收工!下班去!

    +

    前端補正

    +

    記得前端要補上把 prime 帶上來的程式 +

    1
    2
    3
    $.post('/pay-by-prime', {prime: prime}, function(data) {
    alert('付款成功' + JSON.stringify(data))
    })

    +

    完整程式碼

    +

    資料夾結構

    +

    1
    2
    3
    4
    5
    |
    |--- app.js
    |
    |----html
    | |---index.html

    +

    前端

    +

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script text="text/javascript" src="https://js.tappaysdk.com/tpdirect/v2_2_1"></script>
    <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
    <title>Connect payment with TapPay</title>
    </head>

    <body>
    <div style="width: 480px; margin: 50px auto;">
    <label>CardView</label>
    <div id="cardview-container"></div>
    <button id="submit-button" onclick="onClick()">Get Prime</button>
    <pre id="result1"></pre>
    <pre id="result2"></pre>
    </div>
    <script>
    TPDirect.setupSDK(APP_ID, 'APP_KEY', 'sandbox')
    TPDirect.card.setup('#cardview-container')

    var submitButton = document.querySelector('#submit-button')
    var cardViewContainer = document.querySelector('#cardview-container')


    function onClick() {
    TPDirect.card.getPrime(function (result) {
    if (result.status !== 0) {
    console.log('getPrime 錯誤')
    return
    }
    alert('getPrime 成功')
    var prime = result.card.prime
    document.querySelector('#result1').innerHTML = JSON.stringify(result, null, 4)

    $.post('/pay-by-prime', {prime: prime}, function(data) {
    alert('付款成功')
    document.querySelector('#result2').innerHTML = JSON.stringify(data, null, 4)
    })
    })
    }
    </script>
    </body>
    </html>

    +

    後端

    +

    記得先執行以下 command +

    1
    npm install body-parser express

    +

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    const express = require('express')
    const app = express()
    const bodyParser = require('body-parser')
    const https = require('https');
    const PORT = 8080

    app.use(bodyParser.json())
    app.use(bodyParser.urlencoded({
    extended: false
    }))
    app.use('/', express.static(__dirname + "/html")) //serve static content

    app.post('/pay-by-prime', (req, res, next) => {
    const post_data = {
    "prime": req.body.prime,
    "partner_key": "PARTNER_KEY",
    "merchant_id": "MERCHANT_ID",
    "amount": 1,
    "currency": "TWD",
    "details": "An apple and a pen.",
    "cardholder": {
    "phone_number": "+886923456789",
    "name": "jack",
    "email": "example@gmail.com"
    },
    "remember": false
    }

    const post_options = {
    host: 'sandbox.tappaysdk.com',
    port: 443,
    path: '/tpc/payment/pay-by-prime',
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'PARTNER_KEY'
    }
    }

    const post_req = https.request(post_options, function(response) {
    response.setEncoding('utf8');
    response.on('data', function (body) {
    return res.json({
    result: JSON.parse(body)
    })
    });
    });

    post_req.write(JSON.stringify(post_data));
    post_req.end();

    })

    app.listen(PORT, () => {
    console.log('Connet your webiste in the http://localhost:8080/');
    })

    +
diff --git a/2017/09/23/todo-vue/index.html b/2017/09/23/todo-vue/index.html index bed64b435..f8f47171c 100644 --- a/2017/09/23/todo-vue/index.html +++ b/2017/09/23/todo-vue/index.html @@ -19,12 +19,12 @@ - + - + @@ -176,27 +176,41 @@

-

這篇文章主要在記錄如何用 vue + vuex + vue-router
做出一個簡單的 TODO List 專案
DEMO 網站
Source Code

- -

先來訂一個 TODO List 的簡單需求表

    +

    這篇文章主要在記錄如何用 vue + vuex + vue-router +做出一個簡單的 TODO List 專案 +DEMO 網站 +Source Code

    +

    <!--more-->

    +

    先來訂一個 TODO List 的簡單需求表

    +
    1. 能夠輸入項目
    2. 能夠打勾確認完成
    3. 能夠刪除項目
    4. 能夠選擇顯示全部, 未完成, 完成的項目
    -

    程式部分則會分為一個 vuex store 和三個 components

      +

      程式部分則會分為一個 vuex store 和三個 components

      +
      1. 專門控管資料的 store
      2. 輸入項目 component
      3. 顯示項目 compoent
      4. 選擇完成狀態的 component
      -

      專門控管資料的 store

      store.js

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      import Vue from 'vue';
      import Vuex from 'vuex';
      Vue.use(Vuex)
      const store = new Vuex.Store({
      state: {
      lists: [],
      status: '', // 去更新要顯示什麼狀態的項目
      counter: 0 // 當作 increment id 用
      },
      // 宣告可以更改的方式
      mutations: {
      addItem (state, new_item) {
      state.counter += 1
      new_item.id = state.counter
      state.lists.push(new_item)
      },
      changeStatus(state, id) {
      state.lists = state.lists.map((list) => {
      if (list.id === id) list.is_completed = !list.is_completed
      return list
      })
      },
      deleteItem(state, id) {
      state.lists = state.lists.filter((list) => {
      if (list.id === id) return false;
      return true;
      })
      }
      }
      })
      export default store;
      -

      輸入項目 component

      todo-input.vue

      1
      2
      3
      4
      5
      6
      7
      8
      <div>
      <form class="ui form" @submit.prevent="submit">
      <div class="field">
      <label for="">List</label>
      <input type="text" v-model="item">
      </div>
      </form>
      </div>
      -
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      export default {
      data() {
      return {
      item: ''
      }
      },
      methods: {
      submit() {
      this.$store.commit('addItem', {
      name: this.item,
      is_completed: false
      })
      this.item = ''
      }
      }
      }
      -

      顯示項目 component

      todo-item.vue

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      <div>
      <table class="ui table stackable fixed">
      <thead>
      <tr>
      <th colspan="3">Item</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="(list, index) in lists">
      <td :class="{completed: list.is_completed}"> {{list.name}} </td>
      <td>
      <!-- 綁定 done method, 並傳入 id 去做勾選完成-->
      <button class="ui icon inverted green button" @click="done(list.id)">
      <i v-if="list.is_completed === false" class="checkmark icon"></i>
      <i v-else class="reply icon"></i>
      </button>
      </td>
      <td>
      <!-- 綁定 remove method, 並傳入 id 去做刪除 -->
      <button class="ui icon inverted red button" @click="remove(list.id)">
      <i class="trash icon"></i>
      </button>
      </td>
      </tr>
      </tbody>
      </table>
      </div>
      <style scoped lang="css">
      .completed {
      text-decoration: line-through
      }
      </style>
      -
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      export default {
      computed: {
      status () {
      return this.$store.state.status
      },
      lists() {
      return this.$store.getters.filtered_list
      }
      },
      methods: {
      remove(id) {
      this.$store.commit('deleteItem', id)
      },
      done(id) {
      this.$store.commit('changeStatus', id)
      }
      }
      }
      -

      選擇完成狀態的 coomponent

      todo-display.vue

      1
      2
      3
      4
      5
      6
      7
      <div>
      <select class="ui dropdown" v-model="status">
      <option value="">Show All</option>
      <option value="done" selected>Show Done</option>
      <option value="nondone">Show None-done</option>
      </select>
      </div>
      -
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      export default {
      computed: {
      status: {
      get () {
      return this.$store.state.status
      },
      set (value) {
      this.$store.commit('setFilter', value)
      }
      }
      }
      }
      +

      專門控管資料的 store

      +

      store.js

      +

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      import Vue from 'vue';
      import Vuex from 'vuex';
      Vue.use(Vuex)
      const store = new Vuex.Store({
      state: {
      lists: [],
      status: '', // 去更新要顯示什麼狀態的項目
      counter: 0 // 當作 increment id 用
      },
      // 宣告可以更改的方式
      mutations: {
      addItem (state, new_item) {
      state.counter += 1
      new_item.id = state.counter
      state.lists.push(new_item)
      },
      changeStatus(state, id) {
      state.lists = state.lists.map((list) => {
      if (list.id === id) list.is_completed = !list.is_completed
      return list
      })
      },
      deleteItem(state, id) {
      state.lists = state.lists.filter((list) => {
      if (list.id === id) return false;
      return true;
      })
      }
      }
      })
      export default store;

      +

      輸入項目 component

      +

      todo-input.vue

      +

      1
      2
      3
      4
      5
      6
      7
      8
      <div>
      <form class="ui form" @submit.prevent="submit">
      <div class="field">
      <label for="">List</label>
      <input type="text" v-model="item">
      </div>
      </form>
      </div>
      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      export default {
      data() {
      return {
      item: ''
      }
      },
      methods: {
      submit() {
      this.$store.commit('addItem', {
      name: this.item,
      is_completed: false
      })
      this.item = ''
      }
      }
      }

      +

      顯示項目 component

      +

      todo-item.vue

      +

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      <div>
      <table class="ui table stackable fixed">
      <thead>
      <tr>
      <th colspan="3">Item</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="(list, index) in lists">
      <td :class="{completed: list.is_completed}"> {{list.name}} </td>
      <td>
      <!-- 綁定 done method, 並傳入 id 去做勾選完成-->
      <button class="ui icon inverted green button" @click="done(list.id)">
      <i v-if="list.is_completed === false" class="checkmark icon"></i>
      <i v-else class="reply icon"></i>
      </button>
      </td>
      <td>
      <!-- 綁定 remove method, 並傳入 id 去做刪除 -->
      <button class="ui icon inverted red button" @click="remove(list.id)">
      <i class="trash icon"></i>
      </button>
      </td>
      </tr>
      </tbody>
      </table>
      </div>
      <style scoped lang="css">
      .completed {
      text-decoration: line-through
      }
      </style>

      +

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      export default {
      computed: {
      status () {
      return this.$store.state.status
      },
      lists() {
      return this.$store.getters.filtered_list
      }
      },
      methods: {
      remove(id) {
      this.$store.commit('deleteItem', id)
      },
      done(id) {
      this.$store.commit('changeStatus', id)
      }
      }
      }

      +

      選擇完成狀態的 coomponent

      +

      todo-display.vue

      +

      1
      2
      3
      4
      5
      6
      7
      <div>
      <select class="ui dropdown" v-model="status">
      <option value="">Show All</option>
      <option value="done" selected>Show Done</option>
      <option value="nondone">Show None-done</option>
      </select>
      </div>
      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      export default {
      computed: {
      status: {
      get () {
      return this.$store.state.status
      },
      set (value) {
      this.$store.commit('setFilter', value)
      }
      }
      }
      }

      +
diff --git a/2017/10/14/Slack-Bot/index.html b/2017/10/14/Slack-Bot/index.html index ef32e1190..d646f9432 100644 --- a/2017/10/14/Slack-Bot/index.html +++ b/2017/10/14/Slack-Bot/index.html @@ -19,12 +19,12 @@ - + - + @@ -178,10 +178,13 @@

在開始玩弄 Slack Bot 之前,必須要先去申請頁面建立一個 APP

-

申請完之後,可以看到 Features 那邊有很多不同的功能
這次主要會針對 Slash CommandIncoming Webhooks 以及 Interactive Components 做練習

- -

-

在開始正式介紹之前,我們可以思考一個情境
身為工程師,就是會想要降低人工干涉的事情,大量自動化
那今天,我想要自動部署我的 server 的話,可以怎麼做呢?

+

申請完之後,可以看到 Features 那邊有很多不同的功能 +這次主要會針對 Slash CommandIncoming Webhooks 以及 Interactive Components 做練習

+

<!--more--> +

+

在開始正式介紹之前,我們可以思考一個情境 +身為工程師,就是會想要降低人工干涉的事情,大量自動化 +那今天,我想要自動部署我的 server 的話,可以怎麼做呢?

這裡可以透過 Slash Command + Incoming Webhooks 做到,步驟如下

  1. 在 Slack 上面打上 /deploy ticket master (用 Slash Command 通知 server)
  2. @@ -189,9 +192,16 @@

  3. pull 最新版本之後,完成此次更新
  4. 通知公司同仁,更新已結束 (用 Incoming Webhooks 通知)
-

這流程就會是我們所想要的,當然中間還可以透過 jenkins 去部署其他台伺服器
用 slack 部署 server,超方便 der (但感覺拿來訂便當更好用 XD

-

Slash Command

介紹

Slash Command 就是在 Slack 的聊天室下指令,例如

1
/deploy server

就會觸發到遠端伺服器,伺服器解析 command 後,再去近一步做一些行為

-

建立新指令

下圖就是設定 Slash Command 的地方
我們設定了 command 為 /test,然後會用 POST 觸發到遠端的 https://your.website.com/test

+

這流程就會是我們所想要的,當然中間還可以透過 jenkins 去部署其他台伺服器 +用 slack 部署 server,超方便 der (但感覺拿來訂便當更好用 XD

+

Slash Command

+

介紹

+

Slash Command 就是在 Slack 的聊天室下指令,例如 +

1
/deploy server
+就會觸發到遠端伺服器,伺服器解析 command 後,再去近一步做一些行為

+

建立新指令

+

下圖就是設定 Slash Command 的地方 +我們設定了 command 為 /test,然後會用 POST 觸發到遠端的 https://your.website.com/test

比較重要的地方是,Request URL 一定要是 HTTPS,如果不是 HTTPS 一律拒絕,在 Slack 官方文件上面有以下這段說明

@@ -199,47 +209,66 @@

建立新指令

下圖就是設定

按下 Save 之後,回到頁面會看到,就代表建立完成了

-

安裝進到你的 team

按下 Install App to Workspace,就會到授權頁面,然後點下 Authorize 即可安裝完成

+

安裝進到你的 team

+

按下 Install App to Workspace,就會到授權頁面,然後點下 Authorize 即可安裝完成

安裝後在 channel 會出現訊息,通知說已經把 App 加入進來了

-

這時候在聊天室裡面打下 /test 會出現我剛剛建立的 command 和 Description
不過輸入之後,並不會有任何反應,原因是因為我們還沒有設置好伺服器端的設定

+

這時候在聊天室裡面打下 /test 會出現我剛剛建立的 command 和 Description +不過輸入之後,並不會有任何反應,原因是因為我們還沒有設置好伺服器端的設定

-

開始寫程式去接受 slash command

這邊用 nodejs 示範建立一個簡單的伺服器去接受 slash command

+

開始寫程式去接受 slash command

+

這邊用 nodejs 示範建立一個簡單的伺服器去接受 slash command

SSL 的建立容許我這邊就不做示範了 XD (有點麻煩

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.post('/test', (req, res, next) => {
console.log(req.body);
console.log(`User : ${req.body.user_name}`);
console.log(`Text : ${req.body.text}`);
console.log(`Command : ${req.body.command}`);

return res.json({
text: 'Command is successful'
})
})
app.listen(8080)
+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.post('/test', (req, res, next) => {
console.log(req.body);
console.log(`User : ${req.body.user_name}`);
console.log(`Text : ${req.body.text}`);
console.log(`Command : ${req.body.command}`);

return res.json({
text: 'Command is successful'
})
})
app.listen(8080)

在輸入視窗輸入以下指令後

-
1
/test Hi I'm from slack
+

1
/test Hi I'm from slack

伺服器端會得到

-

完整的 JSON 格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 敏感資訊我都以 X 先馬掉了
{
token: 'XXXXXXXXXXXXXXXXXXXXXXX',
team_id: 'XXXXXXXXX',
team_domain: 'XXXXXX',
channel_id: 'XXXXXXXXX',
channel_name: 'announcement',
user_id: 'XXXXXXXXX',
user_name: 'yujack',
command: '/test',
text: 'Hi I\'m from slack',
response_url: 'XXXXXXXXXXXXXX',
trigger_id: 'XXXXXXXXXXXXXX'
}

+

完整的 JSON 格式如下 +

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 敏感資訊我都以 X 先馬掉了
{
token: 'XXXXXXXXXXXXXXXXXXXXXXX',
team_id: 'XXXXXXXXX',
team_domain: 'XXXXXX',
channel_id: 'XXXXXXXXX',
channel_name: 'announcement',
user_id: 'XXXXXXXXX',
user_name: 'yujack',
command: '/test',
text: 'Hi I\'m from slack',
response_url: 'XXXXXXXXXXXXXX',
trigger_id: 'XXXXXXXXXXXXXX'
}

而在輸入窗那邊會看到

-

代表指令有成功到伺服器上面了,然後回傳一個 “Command is successful”
指令完成後,一定會想問一個問題

+

代表指令有成功到伺服器上面了,然後回傳一個 "Command is successful" +指令完成後,一定會想問一個問題

『我想要通知其他人,我觸發了這個指令,我不想要只有我看到,那我該怎麼做?』

這時候就是下一個功能 Incoming Webhooks

-

Incoming Webhooks

介紹

Incoming Webhook,可以直接讓你用 curl 的方式去發訊息到某一個 chaneel 裡面

-

啟用

啟用 Incoming Webhooks 功能

+

Incoming Webhooks

+

介紹

+

Incoming Webhook,可以直接讓你用 curl 的方式去發訊息到某一個 chaneel 裡面

+

啟用

+

啟用 Incoming Webhooks 功能

啟用之後,會在下面看到一個範例,還有新增 Webhook 的地方

-

點選 “Add New Webhook to Workspace”,會到授權頁面
這裡會出現,你想要把訊息可以傳送到哪一個地方
那這裡我就選擇 general 作為範例

+

點選 "Add New Webhook to Workspace",會到授權頁面 +這裡會出現,你想要把訊息可以傳送到哪一個地方 +那這裡我就選擇 general 作為範例

-

使用

在 terminal 貼上以下指令

-
1
curl -X POST -H 'Content-type: application/json' --data '{"text":"Hello, World!"}' https://hooks.slack.com/services/XXXXXXXX
+

使用

+

在 terminal 貼上以下指令

+

1
curl -X POST -H 'Content-type: application/json' --data '{"text":"Hello, World!"}' https://hooks.slack.com/services/XXXXXXXX

在你設定要傳送的那個 channel 就會出現訊息了

-

那有了這個 Webhooks 之後,剛剛的 nodejs server 就可以稍微做更改
這樣的話就可以告訴那一個 channel 的人說,你執行了什麼樣的指令 ~

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));

app.post('/test', (req, res, next) => {
console.log(req.body);
console.log(`User : ${req.body.user_name}`);
console.log(`Text : ${req.body.text}`);
console.log(`Command : ${req.body.command}`);

const command = `curl -X POST ` +
`-H 'Content-type: application/json' ` +
`--data '${JSON.stringify(req.body.slack_message)}' ` +
`https://hooks.slack.com/services/XXXXXXXXX`;

exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
return res.json({
text: 'Command is successful'
})
})
})

app.listen(8080)
-

到這裡不禁會想到一個問題,我能不能不把 branch 記起來
我直接讓 server 告訴我,我在選一個我想要的去 deploy 呢?

-

這時候,Interactive Components 就派上用場了
這個功能可以接收使用者選擇了什麼選項,然後進一步去分析
接下來就要介紹 Interactive Components

-

Interactive Components

介紹

這是一個互動式的功能,在 Slack 上面可能會跳出

+

那有了這個 Webhooks 之後,剛剛的 nodejs server 就可以稍微做更改 +這樣的話就可以告訴那一個 channel 的人說,你執行了什麼樣的指令 ~

+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));

app.post('/test', (req, res, next) => {
console.log(req.body);
console.log(`User : ${req.body.user_name}`);
console.log(`Text : ${req.body.text}`);
console.log(`Command : ${req.body.command}`);

const command = `curl -X POST ` +
`-H 'Content-type: application/json' ` +
`--data '${JSON.stringify(req.body.slack_message)}' ` +
`https://hooks.slack.com/services/XXXXXXXXX`;

exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
return res.json({
text: 'Command is successful'
})
})
})

app.listen(8080)

+

到這裡不禁會想到一個問題,我能不能不把 branch 記起來 +我直接讓 server 告訴我,我在選一個我想要的去 deploy 呢?

+

這時候,Interactive Components 就派上用場了 +這個功能可以接收使用者選擇了什麼選項,然後進一步去分析 +接下來就要介紹 Interactive Components

+

Interactive Components

+

介紹

+

這是一個互動式的功能,在 Slack 上面可能會跳出

  1. Message Button : 例如是否同意這個意見?
  2. Menus : 例如訂 A 便當 or B 便當?
  3. Dialogs : 例如通知?
-

當使用者點選了某一個按鈕或是選擇了其中一個選項
就會 post 到 server 上,跟 server 說使用者做了什麼選擇
學會 Interactive Componet 之後,我們自動化流程就可以改成

+

當使用者點選了某一個按鈕或是選擇了其中一個選項 +就會 post 到 server 上,跟 server 說使用者做了什麼選擇 +學會 Interactive Componet 之後,我們自動化流程就可以改成

  1. 在 Slack 上打 /show ticket (用 Slash Command 通知 server)
  2. Server 回傳 ticket server 所有的 branch (用 Incoming Webhooks 通知)
  3. @@ -248,19 +277,29 @@

    Interactive Components

    pull 最新版本之後,完成此次更新
  4. 通知公司同仁,更新已結束 (用 Incoming Webhooks 通知)
-

啟用

-

使用

在使用 Interactive Componets 之前,要先學會如何製作選項或是按鈕給使用者點選
Slack 官方有提供地方可以客製化不同的按鈕或是表單的地方,點這進去
我客製化了這個訊息

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"text": "Would you like to play a game?",
"attachments": [{
"text": "Choose a game to play",
"fallback": "You are unable to choose a game",
"callback_id": "wopr_game",
"attachment_type": "default",
"actions": [{
"name": "game",
"text": "Chess",
"type": "button",
"value": "chess"
}]
}]
}
+

啟用

+

+

使用

+

在使用 Interactive Componets 之前,要先學會如何製作選項或是按鈕給使用者點選 +Slack 官方有提供地方可以客製化不同的按鈕或是表單的地方,點這進去 +我客製化了這個訊息

+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"text": "Would you like to play a game?",
"attachments": [{
"text": "Choose a game to play",
"fallback": "You are unable to choose a game",
"callback_id": "wopr_game",
"attachment_type": "default",
"actions": [{
"name": "game",
"text": "Chess",
"type": "button",
"value": "chess"
}]
}]
}

拿到訊息之後,利用 Incoming Webhooks 送出到使用者端給使用者點選

-

點選之後伺服器會發 POST 到 https://your.website.com/interactive
伺服器就會收到以下資訊

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Before JSON.Parse
{
payload: '{"actions":[{"name":"game","type":"button","value":"chess"}],"callback_id":"wopr_game","team":{"id":"XXXXXXXXX","domain":"XXXXXX"},"channel":{"id":"XXXXXXXXX","name":"general"},"user":{"id":"XXXXXXXXX","name":"yujack"},"action_ts":"1507970582.644321","message_ts":"1507970575.000002","attachment_id":"1","token":"XXXXXXXXXXXXXXXXXXXXXXX","is_app_unfurl":false,"type":"interactive_message","original_message":{"text":"Would you like to play a game?","bot_id":"XXXXXXXXX","attachments":[{"callback_id":"wopr_game","fallback":"You are unable to choose a game","text":"Choose a game to play","id":1,"actions":[{"id":"1","name":"game","text":"Chess","type":"button","value":"chess","style":""}]}],"type":"message","subtype":"bot_message","ts":"1507970575.000002"},"response_url":"https:\\/\\/hooks.slack.com\\/actions\\/XXXXXXXXX\\/XXXXXXXXX\\/XXXXXXXXXXXXXXXXXXXXXXXXX","trigger_id":"XXXXXXXXX.XXXXXXXXX.XXXXXXXXXXXXXXXXXX"}'
}

// After JSON.parse
{
payload: {
actions: [{
name: 'game',
type: 'button',
value: 'chess'
}],
callback_id: 'wopr_game',
team: {
id: 'XXXXXXXXX',
domain: 'XXXXXX'
},
channel: {
id: 'XXXXXXXXX',
name: 'general'
},
user: {
id: 'XXXXXXXXX',
name: 'yujack'
},
action_ts: '1507970582.644321',
message_ts: '1507970575.000002',
attachment_id: '1',
token: 'XXXXXXXXXXXXXXXXXXXXXXX',
is_app_unfurl: false,
type: 'interactive_message',
original_message: {
text: 'Would you like to play a game?',
bot_id: 'XXXXXXXXX',
attachments: [
[Object]
],
type: 'message',
subtype: 'bot_message',
ts: '1507970575.000002'
},
response_url: 'https://hooks.slack.com/actions/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXX',
trigger_id: 'XXXXXXXXX.XXXXXXXXX.XXXXXXXXXXXXXXXXXX'
}
}
-

按鈕會消失,然後顯示你在 server 上面回傳的完成資訊
收到資訊之後,就可以知道使用者點選了什麼按鈕或是選擇了什麼選項
根據這些選項伺服器在做一些處理就可以完成了

+

點選之後伺服器會發 POST 到 https://your.website.com/interactive +伺服器就會收到以下資訊

+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Before JSON.Parse
{
payload: '{"actions":[{"name":"game","type":"button","value":"chess"}],"callback_id":"wopr_game","team":{"id":"XXXXXXXXX","domain":"XXXXXX"},"channel":{"id":"XXXXXXXXX","name":"general"},"user":{"id":"XXXXXXXXX","name":"yujack"},"action_ts":"1507970582.644321","message_ts":"1507970575.000002","attachment_id":"1","token":"XXXXXXXXXXXXXXXXXXXXXXX","is_app_unfurl":false,"type":"interactive_message","original_message":{"text":"Would you like to play a game?","bot_id":"XXXXXXXXX","attachments":[{"callback_id":"wopr_game","fallback":"You are unable to choose a game","text":"Choose a game to play","id":1,"actions":[{"id":"1","name":"game","text":"Chess","type":"button","value":"chess","style":""}]}],"type":"message","subtype":"bot_message","ts":"1507970575.000002"},"response_url":"https:\\/\\/hooks.slack.com\\/actions\\/XXXXXXXXX\\/XXXXXXXXX\\/XXXXXXXXXXXXXXXXXXXXXXXXX","trigger_id":"XXXXXXXXX.XXXXXXXXX.XXXXXXXXXXXXXXXXXX"}'
}

// After JSON.parse
{
payload: {
actions: [{
name: 'game',
type: 'button',
value: 'chess'
}],
callback_id: 'wopr_game',
team: {
id: 'XXXXXXXXX',
domain: 'XXXXXX'
},
channel: {
id: 'XXXXXXXXX',
name: 'general'
},
user: {
id: 'XXXXXXXXX',
name: 'yujack'
},
action_ts: '1507970582.644321',
message_ts: '1507970575.000002',
attachment_id: '1',
token: 'XXXXXXXXXXXXXXXXXXXXXXX',
is_app_unfurl: false,
type: 'interactive_message',
original_message: {
text: 'Would you like to play a game?',
bot_id: 'XXXXXXXXX',
attachments: [
[Object]
],
type: 'message',
subtype: 'bot_message',
ts: '1507970575.000002'
},
response_url: 'https://hooks.slack.com/actions/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXX',
trigger_id: 'XXXXXXXXX.XXXXXXXXX.XXXXXXXXXXXXXXXXXX'
}
}

+

按鈕會消失,然後顯示你在 server 上面回傳的完成資訊 +收到資訊之後,就可以知道使用者點選了什麼按鈕或是選擇了什麼選項 +根據這些選項伺服器在做一些處理就可以完成了

伺服器上面的程式會長這樣 (這邊單純印出來而已,沒有做後續處理

-
1
2
3
4
5
6
app.post('/interactive', (req, res, next) => {
console.log(req.body);
return res.json({
text: 'Command is successful'
})
})
-

結論

我用了一個情境讓大家比較好思考如何把這三個功能串起來
雖然我還是覺得用在訂便當上面很方便就是了 (?

-

不過有些細部關於真正如何部署或是 SSL 的部分這裡就不會說明了
那個會需要花到一兩篇文章的篇幅去介紹

+

1
2
3
4
5
6
app.post('/interactive', (req, res, next) => {
console.log(req.body);
return res.json({
text: 'Command is successful'
})
})

+

結論

+

我用了一個情境讓大家比較好思考如何把這三個功能串起來 +雖然我還是覺得用在訂便當上面很方便就是了 (?

+

不過有些細部關於真正如何部署或是 SSL 的部分這裡就不會說明了 +那個會需要花到一兩篇文章的篇幅去介紹

如果有任何問題,請歡迎一起來討論 ~

diff --git a/2017/10/17/google-hacking/index.html b/2017/10/17/google-hacking/index.html index dd80f9b5c..95d93f941 100644 --- a/2017/10/17/google-hacking/index.html +++ b/2017/10/17/google-hacking/index.html @@ -19,12 +19,12 @@ - + - + @@ -174,23 +174,46 @@

-

這次要跟大家介紹一下 Google 到底有多好用
相信用過 Google 都知道,Google 的搜尋很方便
但是你知道,Google 還有提供除了關鍵字搜尋以外的各種神奇的搜尋方式嗎 ?

- -

下面這張表就是 Google 提供的各種搜尋技巧
先用幾個來讓大家了解如何使用這個方便的技巧吧!

+

這次要跟大家介紹一下 Google 到底有多好用 +相信用過 Google 都知道,Google 的搜尋很方便 +但是你知道,Google 還有提供除了關鍵字搜尋以外的各種神奇的搜尋方式嗎 ?

+

<!--more-->

+

下面這張表就是 Google 提供的各種搜尋技巧 +先用幾個來讓大家了解如何使用這個方便的技巧吧!

-

site:

假設我想要搜尋我這個網站的,光靠關鍵字搜尋是很難搜尋到的
排名不高,曝光度也不高更是難上加難
但是可以透過以下的方式搜尋到

1
site:yu-jack.github.io

+

site:

+

假設我想要搜尋我這個網站的,光靠關鍵字搜尋是很難搜尋到的 +排名不高,曝光度也不高更是難上加難 +但是可以透過以下的方式搜尋到 +

1
site:yu-jack.github.io

-

intitle:

intitle 就是搜尋 title 呈現的文字
我們可以搜尋一個有趣的東西 “Index Of”

-
1
intitle:"Index Of"
-

可以發現搜尋到一些看起來很像目錄的東西
這個代表這個網站的開發者,沒有適當的處理這個問題
這樣會導致網站的所有目錄曝光在公眾之下
裡面是什麼,我就不點了,有興趣可以試試看

+

intitle:

+

intitle 就是搜尋 title 呈現的文字 +我們可以搜尋一個有趣的東西 "Index Of"

+

1
intitle:"Index Of"

+

可以發現搜尋到一些看起來很像目錄的東西 +這個代表這個網站的開發者,沒有適當的處理這個問題 +這樣會導致網站的所有目錄曝光在公眾之下 +裡面是什麼,我就不點了,有興趣可以試試看

-

inurl:

inurl 就是搜尋 url 之中有沒有包含這個字串
我用以下方式搜尋的話

1
inurl:login

就會發現一堆 url 包含 login 的網址出現

+

inurl:

+

inurl 就是搜尋 url 之中有沒有包含這個字串 +我用以下方式搜尋的話 +

1
inurl:login
+就會發現一堆 url 包含 login 的網址出現

-

filetype

filetype 會去搜尋副檔名,但是他不能單獨使用
必須跟其他指令混在一起使用

-
1
inurl:ntust filetype:pdf
+

filetype

+

filetype 會去搜尋副檔名,但是他不能單獨使用 +必須跟其他指令混在一起使用

+

1
inurl:ntust filetype:pdf

-

結論

這邊做了一點簡單的介紹而已,並沒有作太多詳細介紹
但是可以參考以下的 PDF 去觀看更多不同的技巧
Google Hacking for Penetration
Testers

-

下面這個是公開搜尋 keyword,也許可以直接搜尋到別人不想讓你看到的東西
Google Hacking Database

+

結論

+

這邊做了一點簡單的介紹而已,並沒有作太多詳細介紹 +但是可以參考以下的 PDF 去觀看更多不同的技巧 +Google Hacking for Penetration +Testers

+

下面這個是公開搜尋 keyword,也許可以直接搜尋到別人不想讓你看到的東西 +Google Hacking Database

介紹就到這邊,以後有空會再回來把這篇補詳細

diff --git a/2017/10/19/module-export/index.html b/2017/10/19/module-export/index.html index 4d034188e..87dc79c22 100644 --- a/2017/10/19/module-export/index.html +++ b/2017/10/19/module-export/index.html @@ -19,12 +19,12 @@ - + - + @@ -174,24 +174,30 @@

-

稍微紀錄一下在 nodejs 裡面 module.exports 和 require
以及在 ECMA6 的 export 和 import 的使用方式

- -

nodejs

首先先在 a.js 裡面 export 出一個 object 裡面包含一個 click function
然後再 b.js 裡面用 require a.js,這時候會有兩種使用方式

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// a.js
module.exports = {
click: () => {
console.log('Hi')
}
}

// b.js
// 第一種
const a = require('./a.js')
a.click()
// Hi

// 第二種
const {click} = require('./a.js')
click()
// Hi
+

稍微紀錄一下在 nodejs 裡面 module.exports 和 require +以及在 ECMA6 的 export 和 import 的使用方式

+

<!--more-->

+

nodejs

+

首先先在 a.js 裡面 export 出一個 object 裡面包含一個 click function +然後再 b.js 裡面用 require a.js,這時候會有兩種使用方式

+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// a.js
module.exports = {
click: () => {
console.log('Hi')
}
}

// b.js
// 第一種
const a = require('./a.js')
a.click()
// Hi

// 第二種
const {click} = require('./a.js')
click()
// Hi

另外一種使用方式也可以達到同樣效果

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// a.js
module.exports = () => {
// 這裡可以處理一些初始化的東西
return {
click: () => {
console.log('Hi')
}
}
}

// b.js
// 第一種
const a = require('./a.js')()
a.click()
// Hi

// 第二種
const {click} = require('./a.js')()
click()
// Hi
+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// a.js
module.exports = () => {
// 這裡可以處理一些初始化的東西
return {
click: () => {
console.log('Hi')
}
}
}

// b.js
// 第一種
const a = require('./a.js')()
a.click()
// Hi

// 第二種
const {click} = require('./a.js')()
click()
// Hi

接下來就用不同種例子,看看使用方式

-
1
2
3
4
5
6
7
// a.js
module.exports = [1, 2, 3]

// b.js
const a = require('./a.js')
console.log(a)
// [1, 2, 3]
-
1
2
3
4
5
6
7
8
9
// a.js
module.exports = {
name: 'Hi'
}

// b.js
const a = require('./a.js')
console.log(a.name);
// Hi
-

ECMA6

我把上面的例子轉換成 ECMA6 import 和 export 的方式
但是有些地方會有些許不同

-
1
2
3
4
5
6
7
8
9
10
11
// a.js
export default {
click: () => {
console.log('Hi')
}
}

// b.js
import a from './a.js'
a.click()
// Hi
-
1
2
3
4
5
6
7
8
9
10
11
12
// a.js
const click = () => {
console.log('Hi')
}
export {
click
}

// b.js
import {click} from './a.js'
click()
// Hi
+

1
2
3
4
5
6
7
// a.js
module.exports = [1, 2, 3]

// b.js
const a = require('./a.js')
console.log(a)
// [1, 2, 3]

+

1
2
3
4
5
6
7
8
9
// a.js
module.exports = {
name: 'Hi'
}

// b.js
const a = require('./a.js')
console.log(a.name);
// Hi

+

ECMA6

+

我把上面的例子轉換成 ECMA6 import 和 export 的方式 +但是有些地方會有些許不同

+

1
2
3
4
5
6
7
8
9
10
11
// a.js
export default {
click: () => {
console.log('Hi')
}
}

// b.js
import a from './a.js'
a.click()
// Hi

+

1
2
3
4
5
6
7
8
9
10
11
12
// a.js
const click = () => {
console.log('Hi')
}
export {
click
}

// b.js
import {click} from './a.js'
click()
// Hi

也可以搭配 as 和 * 去做 import (無法跟 export default 做搭配)

-
1
2
3
4
5
6
7
8
9
10
11
12
// a.js
const click = () => {
console.log('Hi')
}
export {
click
}

// b.js
import * as a from './a.js'
a.click()
// Hi
+

1
2
3
4
5
6
7
8
9
10
11
12
// a.js
const click = () => {
console.log('Hi')
}
export {
click
}

// b.js
import * as a from './a.js'
a.click()
// Hi

接下來就用不同種例子,看看使用方式

-
1
2
3
4
5
6
7
// a.js
export default [1, 2, 3]

// b.js
import a from './a.js'
console.log(a);
// [1, 2, 3]
-
1
2
3
4
5
6
7
8
9
10
// a.js
const a = [1, 2, 3]
export {
a
}

// b.js
import {a} from './a.js'
console.log(a);
// [1, 2, 3]
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// a.js
export default {
name: 'hi'
}
// 等同於
const a = {
name: 'hi'
}
export default a

// b.js
import a from './a.js'
console.log(a.name);
// Hi
+

1
2
3
4
5
6
7
// a.js
export default [1, 2, 3]

// b.js
import a from './a.js'
console.log(a);
// [1, 2, 3]

+

1
2
3
4
5
6
7
8
9
10
// a.js
const a = [1, 2, 3]
export {
a
}

// b.js
import {a} from './a.js'
console.log(a);
// [1, 2, 3]

+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// a.js
export default {
name: 'hi'
}
// 等同於
const a = {
name: 'hi'
}
export default a

// b.js
import a from './a.js'
console.log(a.name);
// Hi

+
diff --git a/2017/10/20/secure-header/index.html b/2017/10/20/secure-header/index.html index 70474be38..9679298a3 100644 --- a/2017/10/20/secure-header/index.html +++ b/2017/10/20/secure-header/index.html @@ -19,19 +19,19 @@ - + - + - + @@ -176,32 +176,62 @@

-

最近遇到需要增進網站安全性的問題
於是 survey 了幾個常見的 header 設置方式
接下來會開始介紹每一個 header 的功能以及設置方式
以及可以到這個網站進行檢測 https://securityheaders.io/
個人習慣是用 nodejs + express,所以以下使用方式都會是以 express 為主

-

Set-Cookie 設置方式

防禦面向為: XSS

-

Set-Cookie 基本上是最多人使用的,但是 Set-Cookie 的設置方式如果沒有設定好是不安全的
Set-Cookie 有以下兩個 header 可以設定

+

最近遇到需要增進網站安全性的問題 +於是 survey 了幾個常見的 header 設置方式 +接下來會開始介紹每一個 header 的功能以及設置方式 +以及可以到這個網站進行檢測 https://securityheaders.io/ +個人習慣是用 nodejs + express,所以以下使用方式都會是以 express 為主 +<!--more-->

+

Set-Cookie 設置方式

+

防禦面向為: XSS

+

Set-Cookie 基本上是最多人使用的,但是 Set-Cookie 的設置方式如果沒有設定好是不安全的 +Set-Cookie 有以下兩個 header 可以設定

    -
  1. HttpOnly
    設置 HttpOnly 的 cookie 之後,會沒辦法用 document.cookie 的方式(任何 javascript)去存取 cookie

    +
  2. +

    HttpOnly +設置 HttpOnly 的 cookie 之後,會沒辦法用 document.cookie 的方式(任何 javascript)去存取 cookie

  3. -
  4. Secure
    強制 cookie 只能在 HTTPS protocol 的環境下進行傳遞
    簡單來說設置 Secure 的 cookie 之後在非 HTTPS 的環境底下是會失效的

    +
  5. +

    Secure +強制 cookie 只能在 HTTPS protocol 的環境下進行傳遞 +簡單來說設置 Secure 的 cookie 之後在非 HTTPS 的環境底下是會失效的

-

使用方式

1
2
3
4
res.cookie('cookie_name', 'jack', {
httpOnly: true,
secure: true
})
-

X-XSS-Protection

防禦面向為: XSS

-

設定之後,如果瀏覽器偵測到 XSS 的攻擊,會根據設置的屬性做不同的反應
p.s. 這個是舊有的屬性,基本上可以被 Content-Security-Policy 取代
但是還是可以為那些沒有支援 Content-Security-Policy 的瀏覽器提供一層保護

+

使用方式

+

1
2
3
4
res.cookie('cookie_name', 'jack', {
httpOnly: true,
secure: true
})

+

X-XSS-Protection

+

防禦面向為: XSS

+

設定之後,如果瀏覽器偵測到 XSS 的攻擊,會根據設置的屬性做不同的反應 +p.s. 這個是舊有的屬性,基本上可以被 Content-Security-Policy 取代 +但是還是可以為那些沒有支援 Content-Security-Policy 的瀏覽器提供一層保護

X-XSS-Protection 有以下四個值可以設定

    -
  1. 0
    關閉 XSS 過濾功能

    +
  2. +

    0 +關閉 XSS 過濾功能

  3. -
  4. 1
    開啟 XSS 過濾功能,如果偵測到 XSS 攻擊的話,瀏覽器會刪除不安全的部分

    +
  5. +

    1 +開啟 XSS 過濾功能,如果偵測到 XSS 攻擊的話,瀏覽器會刪除不安全的部分

  6. -
  7. 1; mode=block
    開啟 XSS 過濾功能,如果偵測到 XSS 攻擊的話,瀏覽器不會把網頁給渲染出來

    +
  8. +

    1; mode=block +開啟 XSS 過濾功能,如果偵測到 XSS 攻擊的話,瀏覽器不會把網頁給渲染出來

  9. -
  10. 1;report= (Chromium only)
    開啟 XSS 過濾功能,如果偵測到 XSS 攻擊的話,瀏覽器會回報到指定的 URI

    +
  11. +

    1;report=<reporting-URI> (Chromium only) +開啟 XSS 過濾功能,如果偵測到 XSS 攻擊的話,瀏覽器會回報到指定的 URI

-

使用方式

1
2
3
4
res.setHeader('X-XSS-Protection', '0')
res.setHeader('X-XSS-Protection', '1')
res.setHeader('X-XSS-Protection', '1; mode=block')
res.setHeader('X-XSS-Protection', '1;report=https://www.example.com')
-

Content-Security-Policy

防禦面向為: XSS

-

Content-Security-Policy 是一個可以限制網站的 script object style font 的來源
主要是用白名單的方式限制,甚至可以限制不允許 eval 這種東西出現
簡單來說設定 Content-Security-Policy 之後,只有白名單內的 resource 可以存取
因為值很多種,所以以下用例子來解釋,詳細可以參考 Content-Security-Policy
但基本上有以下幾種可以設定

+

使用方式

+

1
2
3
4
res.setHeader('X-XSS-Protection', '0')
res.setHeader('X-XSS-Protection', '1')
res.setHeader('X-XSS-Protection', '1; mode=block')
res.setHeader('X-XSS-Protection', '1;report=https://www.example.com')

+

Content-Security-Policy

+

防禦面向為: XSS

+

Content-Security-Policy 是一個可以限制網站的 script object style font 的來源 +主要是用白名單的方式限制,甚至可以限制不允許 eval 這種東西出現 +簡單來說設定 Content-Security-Policy 之後,只有白名單內的 resource 可以存取 +因為值很多種,所以以下用例子來解釋,詳細可以參考 Content-Security-Policy +但基本上有以下幾種可以設定

  1. default-src
  2. script-src
  3. @@ -209,51 +239,99 @@

    Content-Security-Policy

  4. font-src
  5. frame-src
-
1
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' *.google.com 'unsafe-eval'; img-src 'self' *.amazonaws.com data:")
-

以上面的例子來說
default-src ‘self’ 代表網站 resource 只能讀取自己網站的,default 代表如果在其他設置欄位沒找到的話,會根據 default-src 為主
script-src ‘self’ .google.com ‘unsafe-eval’ 代表我用的 script src 可以存取自己網站以及 .google.com 底下,以及可以允許 eval
img-src ‘self’ .amazonaws.com data:代表我用的 script src 可以存取自己網站以及 .amazonaws.com 底下,以及比較特別的是可以存取 base64 格式的 image data

-

X-Frame-Options

防禦面向為: Clickjacking

-

X-Frame-Options 主要是設定網站是否能被其他網站透過 iframe frame 的方式遷入
X-Frame-Options 有以下三個值可以設定

+

1
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' *.google.com 'unsafe-eval'; img-src 'self' *.amazonaws.com data:")
+以上面的例子來說 +default-src 'self' 代表網站 resource 只能讀取自己網站的,default 代表如果在其他設置欄位沒找到的話,會根據 default-src 為主 +script-src 'self' *.google.com 'unsafe-eval' 代表我用的 script src 可以存取自己網站以及 *.google.com 底下,以及可以允許 eval +img-src 'self' *.amazonaws.com data:代表我用的 script src 可以存取自己網站以及 *.amazonaws.com 底下,以及比較特別的是可以存取 base64 格式的 image data

+

X-Frame-Options

+

防禦面向為: Clickjacking

+

X-Frame-Options 主要是設定網站是否能被其他網站透過 iframe frame 的方式遷入 +X-Frame-Options 有以下三個值可以設定

    -
  1. DENY
    不允許被任何網站用 iframe 的形式嵌入的
    假設在 www.example.com 設置了 X-Frame-Options: DENY 的話
    www.google.com 的話,是不能 html 裡面嵌入 <iframe src="www.example.com"></iframe>

    +
  2. +

    DENY +不允許被任何網站用 iframe 的形式嵌入的 +假設在 www.example.com 設置了 X-Frame-Options: DENY 的話 +在 www.google.com 的話,是不能 html 裡面嵌入 <iframe src="www.example.com"></iframe>

  3. -
  4. SAMEORIGIN
    允許同源底下的網站,用 iframe 方式嵌入

    +
  5. +

    SAMEORIGIN +允許同源底下的網站,用 iframe 方式嵌入

  6. -
  7. ALLOW-FROM
    設定白名單的 list

    +
  8. +

    ALLOW-FROM +設定白名單的 list

-

使用方式

1
2
3
res.setHeader('X-Frame-Options', 'DENY')
res.setHeader('X-Frame-Options', 'SAMEORIGIN')
res.setHeader('X-Frame-Options', 'ALLOW-FROM https://example.com')
-

X-Content-Type-Options

用途: 避免瀏覽器誤判文件形態

-

X-Content-Type-Options 是拿來防止 Content-Type 被竄改
比較要注意的是,這個屬性只會套用在 script style
如果 stylecontent-type 不是 text/css 就會被拒絕
如果 scriptcontent-type 不是 javascript MIME type 就會被拒絕

-

使用方式

1
res.setHeader('X-Content-Type-Options', 'nosniff')
-

Strict-Transport-Security

防禦面向: 強迫用戶使用 HTTPS,防範 MITM 攻擊

-

Strict-Transport-Security 是強化 HTTPS 機智的一種方式
設置之後,即使是用 HTTP 連線,還是會被轉去使用 HTTPS 連線

-

使用方式

1
res.setHeader('Strict-Transport-Security', 'max-age=16070400; includeSubDomains')
-

Referrer-Policy

防禦面向: 增加隱私權

-

Referrer 代表的是你從 A 網站跳到 B 網站的時候,這個欄位會被記錄為 A
簡單來說,他是記錄你上一個瀏覽的地方的東西

+

使用方式

+

1
2
3
res.setHeader('X-Frame-Options', 'DENY')
res.setHeader('X-Frame-Options', 'SAMEORIGIN')
res.setHeader('X-Frame-Options', 'ALLOW-FROM https://example.com')

+

X-Content-Type-Options

+

用途: 避免瀏覽器誤判文件形態

+

X-Content-Type-Options 是拿來防止 Content-Type 被竄改 +比較要注意的是,這個屬性只會套用在 script style +如果 stylecontent-type 不是 text/css 就會被拒絕 +如果 scriptcontent-type 不是 javascript MIME type 就會被拒絕

+

使用方式

+

1
res.setHeader('X-Content-Type-Options', 'nosniff')

+

Strict-Transport-Security

+

防禦面向: 強迫用戶使用 HTTPS,防範 MITM 攻擊

+

Strict-Transport-Security 是強化 HTTPS 機智的一種方式 +設置之後,即使是用 HTTP 連線,還是會被轉去使用 HTTPS 連線

+

使用方式

+

1
res.setHeader('Strict-Transport-Security', 'max-age=16070400; includeSubDomains')

+

Referrer-Policy

+

防禦面向: 增加隱私權

+

Referrer 代表的是你從 A 網站跳到 B 網站的時候,這個欄位會被記錄為 A +簡單來說,他是記錄你上一個瀏覽的地方的東西

他有以下幾個值可以設定,詳細可以參考這裏

    -
  1. no-referrer
    不允許被記錄下來

    +
  2. +

    no-referrer +不允許被記錄下來

  3. -
  4. origin
    只有紀錄 origin,例如在 https://example.com/a.html 底下,只會傳送 https://example.com

    +
  5. +

    origin +只有紀錄 origin,例如在 https://example.com/a.html 底下,只會傳送 https://example.com

  6. -
  7. strict-origin
    只有在 HTTPS->HTTPS 之間才會被記錄下來

    +
  8. +

    strict-origin +只有在 HTTPS->HTTPS 之間才會被記錄下來

  9. -
  10. no-referrer-when-downgrade (default)
    跟 strict-origin 一樣

    +
  11. +

    no-referrer-when-downgrade (default) +跟 strict-origin 一樣

  12. -
  13. origin-when-cross-origin
    只有在 CORS 的時候, referrer 才會被送出,但只有 origin

    +
  14. +

    origin-when-cross-origin +只有在 CORS 的時候, referrer 才會被送出,但只有 origin

  15. -
  16. same-origin
    CORS 的時候, referrer 不會被記錄,同源的時候會有 origin

    +
  17. +

    same-origin +CORS 的時候, referrer 不會被記錄,同源的時候會有 origin

  18. -
  19. strict-origin-when-cross-origin
    只有在同源的時候才會送出 referrer,而且還是要 HTTPS -> HTTPS

    +
  20. +

    strict-origin-when-cross-origin +只有在同源的時候才會送出 referrer,而且還是要 HTTPS -> HTTPS

  21. -
  22. unsafe-url
    不管怎樣都送就對拉

    +
  23. +

    unsafe-url +不管怎樣都送就對拉

-

使用方式

1
2
res.setHeader('Referrer-Policy', 'no-referrer')
res.setHeader('Referrer-Policy', 'unsafe-url')
-

Public-Key-Pins

防禦面向: 中間人攻擊

-

設定 Public-Key-Pins 之後,可以給予我們是否要主動信任 CA (憑證頒發機構) 的權利
可以防止攻擊者透過 CA 錯誤的簽署憑證並進行中間人攻擊的安全機制

-

使用方式

1
2
3
4
// 裡面的 base64== 是要透過用自己的憑證,產出的 public keu
// 產出的 public key 配合 openssl 產出 fingerprint
// 把 fingerprint 貼上來取代掉 base64== 即可
res.setHeader('Public-Key-Pins', 'pin-sha256="base64=="; max-age=2592000; includeSubDomains')
-

Reference

https://developer.mozilla.org
https://devco.re/blog/2014/03/10/security-issues-of-http-headers-1/
https://devco.re/blog/2014/04/08/security-issues-of-http-headers-2-content-security-policy/
https://devco.re/blog/2014/06/11/setcookie-httponly-security-issues-of-http-headers-3/

+

使用方式

+

1
2
res.setHeader('Referrer-Policy', 'no-referrer')
res.setHeader('Referrer-Policy', 'unsafe-url')

+

Public-Key-Pins

+

防禦面向: 中間人攻擊

+

設定 Public-Key-Pins 之後,可以給予我們是否要主動信任 CA (憑證頒發機構) 的權利 +可以防止攻擊者透過 CA 錯誤的簽署憑證並進行中間人攻擊的安全機制

+

使用方式

+

1
2
3
4
// 裡面的 base64== 是要透過用自己的憑證,產出的 public keu
// 產出的 public key 配合 openssl 產出 fingerprint
// 把 fingerprint 貼上來取代掉 base64== 即可
res.setHeader('Public-Key-Pins', 'pin-sha256="base64=="; max-age=2592000; includeSubDomains')

+

Reference

+

https://developer.mozilla.org +https://devco.re/blog/2014/03/10/security-issues-of-http-headers-1/ +https://devco.re/blog/2014/04/08/security-issues-of-http-headers-2-content-security-policy/ +https://devco.re/blog/2014/06/11/setcookie-httponly-security-issues-of-http-headers-3/

diff --git a/2017/10/24/api-gateway-mapping-template/index.html b/2017/10/24/api-gateway-mapping-template/index.html index a7774de6f..1b5d0df60 100644 --- a/2017/10/24/api-gateway-mapping-template/index.html +++ b/2017/10/24/api-gateway-mapping-template/index.html @@ -19,12 +19,12 @@ - + - + @@ -175,28 +175,69 @@

[Update] 2017-11-08 原本文章的 mapping 方式再依些特別狀況會出錯,在文章最下面加入了最新的 mapping 方式

-

最近需要在 API Gateway 上面作 request 和 response 的參數調整
這裡紀錄一下一些基本的使用語法
官方網站也有提供使用方式還有一些例子
或是可以直接到 Apache Velocity Template Language

- -

if else

1
2
3
4
5
6
7
{
#if ($variable == "cool")
"variable" : "$variable"
#else if ($variable == "hot")
"variable" : "$variable"
#end
}
-

如果參數是 cool 的話,顯示出來是

1
2
3
{
"variable": "cool"
}

-

type

以上一個 case 來說,把 variable 改成是 1

1
2
3
{
"variable": "$variable"
}

-

這樣顯示出來會是

1
2
3
{
"variable": "1"
}

-

但是如果改成這種格式

1
2
3
{
"variable": $variable
}

這樣顯示出來會是
1
2
3
{
"variable": 1
}

-

這邊要注意的是,如果格式是以下這樣,然後參數是 “test”

1
2
3
{
"variable": $variable
}

這樣顯示出來會是
1
2
3
4
{
// 這會直接讓 API Gateway mapping template 直接爆炸
"variable": test
}

-

key

如果把 $variable 設成 “test”,並用以下的 template

1
2
3
{
"$variable": "$variable"
}

結果會是
1
2
3
{
"test": "test"
}

-

foreach and keySet

資料如下

1
2
3
4
5
6
7
8
9
10
11
12
{
"data": {
"book": [{
"title": "cool",
"serial": 123
}, {
"title": "hot",
"serial": 321
}]
},
"comment": "Hi"
}

-

我想要把他轉換成以下的格式,該怎麼用 mapping template

1
2
3
4
5
6
7
8
9
10
{
"book_library": [{
"name": "cool",
"number": 123
}, {
"name": "hot",
"number": 321
}],
"message": "Hi"
}

-

mapping template 可以這樣寫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#set($root = $input.path("$"))
{
// keySet 可以拿到這層所有的 key
// 這裡可以拿到 data 和 comment ($rootKey)
#foreach($rootKey in $root.keySet())
#if($rootKey == "data")
"book_library": [
#foreach($elem in $root.get($rootKey))
{
// 這層可以達到 title 和 serial
#foreach($i in $elem.keySet())
#if($i == "title")
"name": "$elem.get($i)"
#elseif($i == "serial")
// 因為要讓這裡是數字,所以不加上雙引號
"number": $elem.get($i)
#end
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
]
#elseif ($rootKey == "comment")
"message": "$root.get($rootKey)"
#end


// 這是為了讓
// {
// "test": 123
// }
// 最後面的 123 加逗點用的
// 如果是會後一個,就不會加逗點了
#if($foreach.hasNext),#end
#end
}

-

更好的寫法

在 aws 官網中,除了拿到 raw payload 之外
還可以利用 $input.json() 的寫法拿到格式更完整的資料
因為在原本的方式中,如果拿到的字串包含 \n,這會讓 API Gateway 爆炸
雖然可以透過 $util.escapeJavaScript 的方式避免
但在每一個地方都加上 $util.escapeJavaScript 也是很蠢
所以新的寫法會像是這樣

+

最近需要在 API Gateway 上面作 request 和 response 的參數調整 +這裡紀錄一下一些基本的使用語法 +官方網站也有提供使用方式還有一些例子 +或是可以直接到 Apache Velocity Template Language

+

<!--more-->

+

if else

+

1
2
3
4
5
6
7
{
#if ($variable == "cool")
"variable" : "$variable"
#else if ($variable == "hot")
"variable" : "$variable"
#end
}

+

如果參數是 cool 的話,顯示出來是 +

1
2
3
{
"variable": "cool"
}

+

type

+

以上一個 case 來說,把 variable 改成是 1 +

1
2
3
{
"variable": "$variable"
}

+

這樣顯示出來會是 +

1
2
3
{
"variable": "1"
}

+

但是如果改成這種格式 +

1
2
3
{
"variable": $variable
}
+這樣顯示出來會是 +
1
2
3
{
"variable": 1
}

+

這邊要注意的是,如果格式是以下這樣,然後參數是 "test" +

1
2
3
{
"variable": $variable
}
+這樣顯示出來會是 +
1
2
3
4
{
// 這會直接讓 API Gateway mapping template 直接爆炸
"variable": test
}

+

key

+

如果把 $variable 設成 "test",並用以下的 template +

1
2
3
{
"$variable": "$variable"
}
+結果會是 +
1
2
3
{
"test": "test"
}

+

foreach and keySet

+

資料如下 +

1
2
3
4
5
6
7
8
9
10
11
12
{
"data": {
"book": [{
"title": "cool",
"serial": 123
}, {
"title": "hot",
"serial": 321
}]
},
"comment": "Hi"
}

+

我想要把他轉換成以下的格式,該怎麼用 mapping template +

1
2
3
4
5
6
7
8
9
10
{
"book_library": [{
"name": "cool",
"number": 123
}, {
"name": "hot",
"number": 321
}],
"message": "Hi"
}

+

mapping template 可以這樣寫 +

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#set($root = $input.path("$"))
{
// keySet 可以拿到這層所有的 key
// 這裡可以拿到 data 和 comment ($rootKey)
#foreach($rootKey in $root.keySet())
#if($rootKey == "data")
"book_library": [
#foreach($elem in $root.get($rootKey))
{
// 這層可以達到 title 和 serial
#foreach($i in $elem.keySet())
#if($i == "title")
"name": "$elem.get($i)"
#elseif($i == "serial")
// 因為要讓這裡是數字,所以不加上雙引號
"number": $elem.get($i)
#end
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
]
#elseif ($rootKey == "comment")
"message": "$root.get($rootKey)"
#end


// 這是為了讓
// {
// "test": 123
// }
// 最後面的 123 加逗點用的
// 如果是會後一個,就不會加逗點了
#if($foreach.hasNext),#end
#end
}

+

更好的寫法

+

在 aws 官網中,除了拿到 raw payload 之外 +還可以利用 $input.json() 的寫法拿到格式更完整的資料 +因為在原本的方式中,如果拿到的字串包含 \n,這會讓 API Gateway 爆炸 +雖然可以透過 $util.escapeJavaScript 的方式避免 +但在每一個地方都加上 $util.escapeJavaScript 也是很蠢 +所以新的寫法會像是這樣

    -
  1. 第一個地方是 #set($count = $foreach.count - 1) 這是為了拿到 index

    +
  2. +

    第一個地方是 #set($count = $foreach.count - 1) 這是為了拿到 index

  3. -
  4. 第二個地方寫法就比較特別,拿到 index 之後,$input.json($) 這樣是拿到整個 payload (JSON)
    如果 $rootKey = 'book_library' 那這樣寫$input.json("$['$rootKey']") 等於 $input.json("$['book_library']") 的寫法,就可以拿到陣列了。
    那如果要拿第一個的話
    $input.json("$['$rootKey'][0]") 這樣就能拿到, 如果用變數取代的話,可以寫成
    $input.json("$['$rootKey'][$count]")
    拿到陣列後,要拿陣列裡面的物件就可以這樣寫
    $input.json("$['$rootKey'][0]['$i']") 等同於 $input.json("$['$rootKey'][0]['title']")

    +
  5. +

    第二個地方寫法就比較特別,拿到 index 之後,$input.json($) 這樣是拿到整個 payload (JSON) +如果 $rootKey = 'book_library' 那這樣寫$input.json("$['$rootKey']") 等於 $input.json("$['book_library']") 的寫法,就可以拿到陣列了。 +那如果要拿第一個的話 +$input.json("$['$rootKey'][0]") 這樣就能拿到, 如果用變數取代的話,可以寫成 +$input.json("$['$rootKey'][$count]") +拿到陣列後,要拿陣列裡面的物件就可以這樣寫 +$input.json("$['$rootKey'][0]['$i']") 等同於 $input.json("$['$rootKey'][0]['title']")

  6. -
  7. 第三個就是讓剩餘的都直接拿出來就結束了

    +
  8. +

    第三個就是讓剩餘的都直接拿出來就結束了

-

要特別注意的點是,不用加上 “” 在 $input.json() 外面了
因為用 $input.json() 拿的已經是完整格式了
String 就是 String,不用像上面的方式還要加上 “” 去讓他變成字串
Boolean Int 等等全部都是,也不用擔心 \n 這個出現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#set($root = $input.path("$"))
{
// keySet 可以拿到這層所有的 key
// 這裡可以拿到 data 和 comment ($rootKey)
#foreach($rootKey in $root.keySet())
#if($rootKey == "data")
"book_library": [
#foreach($elem in $root.get($rootKey))
{
// ============= Here =================
#set($count = $foreach.count - 1)
// ============= Here =================

// 這層可以達到 title 和 serial
#foreach($i in $elem.keySet())
// ============= Here =================
#if($i == "title")
"name": $input.json("$['$rootkey'][$count]['$i']")
#elseif($i == "serial")
"number": $input.json("$['$rootkey'][$count]['$i']")
#end
// ============= Here =================
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
]
#elseif ($rootKey == "comment")
// =============== Here ==============
"message": $input.json("$.$rootkey")
// =============== Here ==============
#end


// 這是為了讓
// {
// "test": 123
// }
// 最後面的 123 加逗點用的
// 如果是會後一個,就不會加逗點了
#if($foreach.hasNext),#end
#end
}

+

要特別注意的點是,不用加上 "" 在 $input.json() 外面了 +因為用 $input.json() 拿的已經是完整格式了 +String 就是 String,不用像上面的方式還要加上 "" 去讓他變成字串 +Boolean Int 等等全部都是,也不用擔心 \n 這個出現 +

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#set($root = $input.path("$"))
{
// keySet 可以拿到這層所有的 key
// 這裡可以拿到 data 和 comment ($rootKey)
#foreach($rootKey in $root.keySet())
#if($rootKey == "data")
"book_library": [
#foreach($elem in $root.get($rootKey))
{
// ============= Here =================
#set($count = $foreach.count - 1)
// ============= Here =================

// 這層可以達到 title 和 serial
#foreach($i in $elem.keySet())
// ============= Here =================
#if($i == "title")
"name": $input.json("$['$rootkey'][$count]['$i']")
#elseif($i == "serial")
"number": $input.json("$['$rootkey'][$count]['$i']")
#end
// ============= Here =================
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
]
#elseif ($rootKey == "comment")
// =============== Here ==============
"message": $input.json("$.$rootkey")
// =============== Here ==============
#end


// 這是為了讓
// {
// "test": 123
// }
// 最後面的 123 加逗點用的
// 如果是會後一個,就不會加逗點了
#if($foreach.hasNext),#end
#end
}

diff --git a/2017/11/01/how-to-test/index.html b/2017/11/01/how-to-test/index.html index b2b4f6210..3e5079f6a 100644 --- a/2017/11/01/how-to-test/index.html +++ b/2017/11/01/how-to-test/index.html @@ -19,12 +19,12 @@ - + - + @@ -175,37 +175,62 @@

-

為什麼要測試?
確保你程式的結果跟你預期所想的一樣
那這樣有什麼好處?
這樣大概會讓你少加班好幾小時吧 ….

-

下面我會介紹如何用 mocha 去做測試
小弟我對測試並沒有鑽研到很深的地步,如果有任何奇怪的地方,歡迎指教 ~

- -

介紹

測試是為了確保你的程式結果跟你預期所想的一樣
那我們又該如何去測試?那又該測試什麼東西?

-

在這邊我把該測試的東西分成三個方向,由小到大
這篇文章重點會放在 Unit Test 的部分,其他會以 Unit Test 的概念延伸說明

+

為什麼要測試? +確保你程式的結果跟你預期所想的一樣 +那這樣有什麼好處? +這樣大概會讓你少加班好幾小時吧 ....

+

下面我會介紹如何用 mocha 去做測試 +小弟我對測試並沒有鑽研到很深的地步,如果有任何奇怪的地方,歡迎指教 ~

+

<!-- more -->

+

介紹

+

測試是為了確保你的程式結果跟你預期所想的一樣 +那我們又該如何去測試?那又該測試什麼東西?

+

在這邊我把該測試的東西分成三個方向,由小到大 +這篇文章重點會放在 Unit Test 的部分,其他會以 Unit Test 的概念延伸說明

    -
  1. Unit Test (本篇重點)
    測試你的 function 有沒有輸出正確結果

    +
  2. +

    Unit Test (本篇重點) +測試你的 function 有沒有輸出正確結果

  3. -
  4. API Test
    測試跟 API 相關的 Unit 有沒有正確執行

    +
  5. +

    API Test +測試跟 API 相關的 Unit 有沒有正確執行

  6. -
  7. User Story Test
    測試整個使用情景有沒有跟使用者所想的一樣

    +
  8. +

    User Story Test +測試整個使用情景有沒有跟使用者所想的一樣

-

準備

在開始要做測試之前需要安裝以下幾樣東西

1
2
3
4
// mocha 測試主要會用到的東西
// chai 一個很好用的 assertion library
// axios 發 request 用的 library
npm install mocha chai axios

+

準備

+

在開始要做測試之前需要安裝以下幾樣東西 +

1
2
3
4
// mocha 測試主要會用到的東西
// chai 一個很好用的 assertion library
// axios 發 request 用的 library
npm install mocha chai axios

該建立的資料夾

-
1
2
3
4
5

|--- package.json
|--- node_modules
|--- test
| |--- test.js
-

如何測試

想像一下我們現在有一個需求進來了
『我要把我丟進去的數字都變成一個陣列然後回傳回來』

-

所以根據這個狀況我可以列出一個測試的方式

1
2
3
4
5
6
7
8
9
10
// First Test Case in test.js

const {assert} = require('chai')
describe('Unit Test', function() {
it('Test function with one number', function () {
const result = transformToArray(1)
assert.equal(typeof [], typeof result)
assert.equal(1, result.length)
});
})

測試列出來了,但是程式完全還沒寫
於是接下來先寫主要功能的程式

-
1
2
3
4
5
6
7
8
// 這程式想放哪都可以,記得 require 近來就好

function transformToArray (number) {
return [number]
}

transformToArray(1)
// result should be [1]
-

程式寫出來之後,可以正式執行測試了
依照這個 test case 我們的程式是有正確執行的

+

1
2
3
4
5

|--- package.json
|--- node_modules
|--- test
| |--- test.js

+

如何測試

+

想像一下我們現在有一個需求進來了 +『我要把我丟進去的數字都變成一個陣列然後回傳回來』

+

所以根據這個狀況我可以列出一個測試的方式 +

1
2
3
4
5
6
7
8
9
10
// First Test Case in test.js

const {assert} = require('chai')
describe('Unit Test', function() {
it('Test function with one number', function () {
const result = transformToArray(1)
assert.equal(typeof [], typeof result)
assert.equal(1, result.length)
});
})
+測試列出來了,但是程式完全還沒寫 +於是接下來先寫主要功能的程式

+

1
2
3
4
5
6
7
8
// 這程式想放哪都可以,記得 require 近來就好

function transformToArray (number) {
return [number]
}

transformToArray(1)
// result should be [1]

+

程式寫出來之後,可以正式執行測試了 +依照這個 test case 我們的程式是有正確執行的

-

接下來我在列出另一個 test case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// First Test Case in test.js

const {assert} = require('chai')
describe('Unit Test', function() {
it('Test function with one number', function () {
const result = transformToArray(1)
assert.equal(typeof [], typeof result)
assert.equal(1, result.length)
});
it('Test function with multiple numbers', function () {
const result = transformToArray(1, 2, 3, 4)
assert.equal(typeof result, typeof [])
assert.equal(result.length, 4)
})
})

-

Oops, test case 出錯了,代表我的程式爆炸了
這時候該怎麼辦?
那就是回去繼續修改我的程式讓他可以通過這個 test case

+

接下來我在列出另一個 test case +

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// First Test Case in test.js

const {assert} = require('chai')
describe('Unit Test', function() {
it('Test function with one number', function () {
const result = transformToArray(1)
assert.equal(typeof [], typeof result)
assert.equal(1, result.length)
});
it('Test function with multiple numbers', function () {
const result = transformToArray(1, 2, 3, 4)
assert.equal(typeof result, typeof [])
assert.equal(result.length, 4)
})
})

+

Oops, test case 出錯了,代表我的程式爆炸了 +這時候該怎麼辦? +那就是回去繼續修改我的程式讓他可以通過這個 test case

進行修改後,程式變成這樣

-
1
2
3
4
5
6
7
8
9
// Version 2 程式

const transformToArray = function () {
let temp = []
for (const i of arguments) {
temp.push(arguments[i])
}
return temp
}
+

1
2
3
4
5
6
7
8
9
// Version 2 程式

const transformToArray = function () {
let temp = []
for (const i of arguments) {
temp.push(arguments[i])
}
return temp
}

登愣,我們執行結果正確了

但是總覺得程式好像沒有寫得很漂亮於是改成

-
1
2
3
4
5
6
7
// Version 3 的程式

const transformToArray = function () {
return Object.keys(arguments).map((key) => {
return arguments[key]
})
}
-

在我們剛剛列出 test case 然後修正程式去符合新的 test case
這整個開發流程,就屬於 TDD 的方式

+

1
2
3
4
5
6
7
// Version 3 的程式

const transformToArray = function () {
return Object.keys(arguments).map((key) => {
return arguments[key]
})
}

+

在我們剛剛列出 test case 然後修正程式去符合新的 test case +這整個開發流程,就屬於 TDD 的方式

  1. 列出 test case
  2. 開發程式
  3. @@ -217,16 +242,25 @@

    如何測試

    想像一下我們現在

  4. Test-driven Development 的方式,是以測試為主,列出各種 test case 讓程式可以正確執行
  5. Behavior-driven Development 的方式跟 TDD 很相似,但是他會以規格為主(有點像訂出 User Story 的感覺)
  6. -

    BDD 比較符合我們現時開發上的流程,客戶需求進來
    變成一個 User Story,根據 User Story 寫出 Test Case
    接下來就是開發程式,讓程式可以通過這個 Test Case

    -

    那關於測試 API 和 Uesr Story 的方式
    大體上跟 Unit Test 很相似,差在 Test Case 的寫法不太一樣而已

    -

    對 API Test 來說,可能是 3 ~ 4 Unit 合成的一個 API
    例如 API 是『登入』,對登入來說 Input 是帳號密碼,Output 是有無驗證成功
    帳號密碼的驗證可能牽扯到 3 ~ 4 Unit,但是這已經在 Unit Test 那邊完成了
    所以對於 API Test 來說,可能會列出以下幾種 Test Case

    +

    BDD 比較符合我們現時開發上的流程,客戶需求進來 +變成一個 User Story,根據 User Story 寫出 Test Case +接下來就是開發程式,讓程式可以通過這個 Test Case

    +

    那關於測試 API 和 Uesr Story 的方式 +大體上跟 Unit Test 很相似,差在 Test Case 的寫法不太一樣而已

    +

    對 API Test 來說,可能是 3 ~ 4 Unit 合成的一個 API +例如 API 是『登入』,對登入來說 Input 是帳號密碼,Output 是有無驗證成功 +帳號密碼的驗證可能牽扯到 3 ~ 4 Unit,但是這已經在 Unit Test 那邊完成了 +所以對於 API Test 來說,可能會列出以下幾種 Test Case

    1. 輸入正確帳號密碼,成功登入
    2. 輸入錯誤帳號密碼,無法登入
    3. 輸入正確帳號錯誤密碼,無法登入
    4. 輸入錯誤帳號正確密碼,無法登入
    -

    對 User Story 來說,可能是 3 ~ 4 個 API 合成的一個功能
    假如使用情形是,使用者登入了賣書網站
    搜尋了他想要的書本,根據搜尋會顯示或是找不到書本給使用者看
    那對於 User Story Test 來說,可能會列出以下幾種 Test Case

    +

    對 User Story 來說,可能是 3 ~ 4 個 API 合成的一個功能 +假如使用情形是,使用者登入了賣書網站 +搜尋了他想要的書本,根據搜尋會顯示或是找不到書本給使用者看 +那對於 User Story Test 來說,可能會列出以下幾種 Test Case

    First Test Case

    1. 輸入正確帳號密碼,成功登入後
    2. @@ -237,7 +271,11 @@

      如何測試

      想像一下我們現在

    3. 輸入正確帳號密碼,成功登入後
    4. 在搜尋欄位輸入『找不到』,然後顯示搜尋結果為 0 筆的頁面
    -

    結語

    我認為用什麼樣的開發流程去測試程式都可以
    BDD TDD ATDD 等等,都是很好的開發流程
    對於不同團隊都會有各個團隊習慣的方式
    但最重要的是,要有『測試』這件事情出現在專案的開發流程上就足以

    +

    結語

    +

    我認為用什麼樣的開發流程去測試程式都可以 +BDD TDD ATDD 等等,都是很好的開發流程 +對於不同團隊都會有各個團隊習慣的方式 +但最重要的是,要有『測試』這件事情出現在專案的開發流程上就足以

diff --git a/2017/11/04/handle-file-with-Lambda-and-API-Gateway/index.html b/2017/11/04/handle-file-with-Lambda-and-API-Gateway/index.html index 83518ecad..4c86fc9b7 100644 --- a/2017/11/04/handle-file-with-Lambda-and-API-Gateway/index.html +++ b/2017/11/04/handle-file-with-Lambda-and-API-Gateway/index.html @@ -19,12 +19,12 @@ - + - + @@ -177,42 +177,66 @@

-

這篇主要是記錄如何利用 AWS lambda 和 AWS API Gateway 做檔案的上傳以及下載
在 API Gateway 中要做幾項設定才有辦法達成
加上 Lambda 不能回傳『完整』的 binary 所以必須搭配 API Gateway mapping template 調整
這篇不會一步一步教學開 API Gateway 和 Lambda,只記錄重點部分

-

API Gateway

主要調整得地方有兩個

+

這篇主要是記錄如何利用 AWS lambda 和 AWS API Gateway 做檔案的上傳以及下載 +在 API Gateway 中要做幾項設定才有辦法達成 +加上 Lambda 不能回傳『完整』的 binary 所以必須搭配 API Gateway mapping template 調整 +這篇不會一步一步教學開 API Gateway 和 Lambda,只記錄重點部分 +<!-- more -->

+

API Gateway

+

主要調整得地方有兩個

  1. /upload Integration Request
  2. /download Integration Response
-

另外還有一種特別的方式,是利用 API Gateway 的 Binary Support 去處理
這種方式會列在最後面

-

/upload Integration Request

    -
  1. 到 body mapping template 底下調整成圖片樣子(Generate templaye 選擇 “Method Request passthrough”)
  2. +

    另外還有一種特別的方式,是利用 API Gateway 的 Binary Support 去處理 +這種方式會列在最後面

    +

    /upload Integration Request

    +
      +
    1. 到 body mapping template 底下調整成圖片樣子(Generate templaye 選擇 "Method Request passthrough")

    -
    1
    2
    3
    4
    5
    // 需要修改的部分為第一行的 body
    // 其他行不需要做調整
    {
    "body": "$util.base64Encode($input.body)"
    }
    -

    /download Integration Reponse

      +

      1
      2
      3
      4
      5
      // 需要修改的部分為第一行的 body
      // 其他行不需要做調整
      {
      "body": "$util.base64Encode($input.body)"
      }

      +

      /download Integration Reponse

      +
      1. 到 body mapping template 底下調整成圖片樣子
      -


      1
      $util.base64Decode($input.body)

      -

      Lambda

      主要是用 nodejs 去編寫處理上傳的部分

      -

      Handle upload request

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const multipart = require('parse-multipart');
      exports.handler = (event, context, callback) => {
      // convert base64 string to binary
      const buffer = new Buffer(event.body, 'base64')
      const boundary = multipart.getBoundary(event.params.header['Content-Type'])
      const parts = multipart.Parse(buffer, boundary)
      return callback(null, {
      s: parts
      })

      }
      -

      Handle download request

      這邊範例是用去讀取 S3 的檔案

      -
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      exports.handler = (event, context, callback) => {

      s3.getObject({
      Bucket: 'your-bucket',
      Key: 'download_file.json'
      }, (err, data) => {
      if (err) {
      return callback(err)
      }
      // 原本方式是會直接回傳 JSON (DEMO 有圖)
      // callback(null, data.Body)
      //
      // 正確方式,回傳 base64,然後讓 API Gateway 去 decode
      callback(null, new Buffer(data.Body).toString('base64'))
      })
      }
      -

      Demo with Postman

      Upload File

      上傳要注意選 “form-data”
      然後隨便選擇一個檔案即可

      +

      +

      1
      $util.base64Decode($input.body)

      +

      Lambda

      +

      主要是用 nodejs 去編寫處理上傳的部分

      +

      Handle upload request

      +

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const multipart = require('parse-multipart');
      exports.handler = (event, context, callback) => {
      // convert base64 string to binary
      const buffer = new Buffer(event.body, 'base64')
      const boundary = multipart.getBoundary(event.params.header['Content-Type'])
      const parts = multipart.Parse(buffer, boundary)
      return callback(null, {
      s: parts
      })

      }

      +

      Handle download request

      +

      這邊範例是用去讀取 S3 的檔案

      +

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      exports.handler = (event, context, callback) => {

      s3.getObject({
      Bucket: 'your-bucket',
      Key: 'download_file.json'
      }, (err, data) => {
      if (err) {
      return callback(err)
      }
      // 原本方式是會直接回傳 JSON (DEMO 有圖)
      // callback(null, data.Body)
      //
      // 正確方式,回傳 base64,然後讓 API Gateway 去 decode
      callback(null, new Buffer(data.Body).toString('base64'))
      })
      }

      +

      Demo with Postman

      +

      Upload File

      +

      上傳要注意選 "form-data" +然後隨便選擇一個檔案即可

      -

      Download File

      如果沒有在 API Gateway 做調整的話會變成
      沒錯,Lambda 是會直接回傳 JSON 的
      他並不會回傳 binary 給你,所以才要到 API Gateway 和 Lambda 做一些調整 (要到 mapping template 調整)

      +

      Download File

      +

      如果沒有在 API Gateway 做調整的話會變成 +沒錯,Lambda 是會直接回傳 JSON 的 +他並不會回傳 binary 給你,所以才要到 API Gateway 和 Lambda 做一些調整 (要到 mapping template 調整)

      修改之後

      然後可以改用程式下載檔案

      -
      1
      2
      3
      4
      5
      6
      const request = require('request')
      const fs = require('fs')
      const r = request.post('your_url')
      r.on('response', function (res) {
      res.pipe(fs.createWriteStream('download_file.json'))
      });
      -

      額外補充 - Binary Support

      /upload integration request

      在 API Gateway 底下的 binary support 加上 multipart/form-data,API Gateway 就會自動幫我們做 base64 encode

      -

      而在 mapping template 就改成這樣即可
      lambda 不需要做任何調整

      -
      1
      2
      3
      {
      "body": $input.json('$')
      }
      -

      註記

        +

        1
        2
        3
        4
        5
        6
        const request = require('request')
        const fs = require('fs')
        const r = request.post('your_url')
        r.on('response', function (res) {
        res.pipe(fs.createWriteStream('download_file.json'))
        });

        +

        額外補充 - Binary Support

        +

        /upload integration request

        +

        在 API Gateway 底下的 binary support 加上 multipart/form-data,API Gateway 就會自動幫我們做 base64 encode

        +

        而在 mapping template 就改成這樣即可 +lambda 不需要做任何調整

        +

        1
        2
        3
        {
        "body": $input.json('$')
        }

        +

        註記

        +
        1. API Gateway payload 有限制 10mb
        2. Lambda 有限制 6mb
        -

        所以最大只能上傳或下載 6mb 的檔案
        但是,因為會轉成 base64,所以原本的 4mb 轉完可能變成 5mb
        這裡是特別要注意的地方

        +

        所以最大只能上傳或下載 6mb 的檔案 +但是,因為會轉成 base64,所以原本的 4mb 轉完可能變成 5mb +這裡是特別要注意的地方

diff --git a/2017/11/08/ngrok/index.html b/2017/11/08/ngrok/index.html index 7def47e42..63767c83e 100644 --- a/2017/11/08/ngrok/index.html +++ b/2017/11/08/ngrok/index.html @@ -19,12 +19,12 @@ - + - + @@ -174,13 +174,19 @@

-

今天要介紹的是一個非常好用的東西,可以直接讓大家都連到你的 localhost
這樣做完一個網站,你也不用特地部署,可以直接透過這個工具,大家都能連到

- +

今天要介紹的是一個非常好用的東西,可以直接讓大家都連到你的 localhost +這樣做完一個網站,你也不用特地部署,可以直接透過這個工具,大家都能連到

+

<!-- more -->

工具連結在此: Ngrok

-

使用方式簡單介紹

下載下來後,unzip 之後就可以做使用了
如果在 localhost 開了一個 8080 想讓大家連
可以在下這以下這行指令

-
1
./ngrok http 8080
-

結果會長這樣,然後在網址列打上他給你的網址就可以直接連到你的 8080 port

-

如果像是要用到 Apple Pay 一些特定服務只允許跑在 SSL 上面的話
這個工具會非常有用,但畢竟是經過別人家重導 …. 所以小心用

+

使用方式簡單介紹

+

下載下來後,unzip 之後就可以做使用了 +如果在 localhost 開了一個 8080 想讓大家連 +可以在下這以下這行指令

+

1
./ngrok http 8080

+

結果會長這樣,然後在網址列打上他給你的網址就可以直接連到你的 8080 port +

+

如果像是要用到 Apple Pay 一些特定服務只允許跑在 SSL 上面的話 +這個工具會非常有用,但畢竟是經過別人家重導 .... 所以小心用

diff --git a/2017/11/15/handle-upload-download-file-with-Lambda-and-API-Gateway-2/index.html b/2017/11/15/handle-upload-download-file-with-Lambda-and-API-Gateway-2/index.html index 7c6c64f73..3227b7550 100644 --- a/2017/11/15/handle-upload-download-file-with-Lambda-and-API-Gateway-2/index.html +++ b/2017/11/15/handle-upload-download-file-with-Lambda-and-API-Gateway-2/index.html @@ -19,12 +19,12 @@ - + - + @@ -177,19 +177,38 @@

-

前言

這次記錄是介紹,只透過 AWS API Gateway 不加上 AWS Lambda 做檔案的上傳
上一篇因為 Lambda 的特性是 Request 和 Response 都要是 JSON
所以必須在 API Gateway 必須要做 body mapping 的調整
e.g 透過 Binary Support 或是 Base64Enconde 的方式處理
那這次的紀錄是讓 AWS 的 API Gateway 的 Upload 直接通往到後面的 Server 端

-

AWS API Gateway

在上一篇,透過 Lambda 和 API Gateway 完成檔案上傳和下載之後
出現了一個疑問,API Gateway 直接到 Server 這端,需不需要調整東西呢 ?

+

前言

+

這次記錄是介紹,只透過 AWS API Gateway 不加上 AWS Lambda 做檔案的上傳 +上一篇因為 Lambda 的特性是 Request 和 Response 都要是 JSON +所以必須在 API Gateway 必須要做 body mapping 的調整 +e.g 透過 Binary Support 或是 Base64Enconde 的方式處理 +那這次的紀錄是讓 AWS 的 API Gateway 的 Upload 直接通往到後面的 Server 端 +<!-- more -->

+

AWS API Gateway

+

在上一篇,透過 Lambda 和 API Gateway 完成檔案上傳和下載之後 +出現了一個疑問,API Gateway 直接到 Server 這端,需不需要調整東西呢 ?

在這樣的想法下,做了一個簡單的實現

  1. 在 API Gateway 新增一個 API /upload (POST Method)
  2. -
  3. 用 nodejs 啟動 server (記得把 body-parser 改成 text 也支援的設定)
    在這樣的實驗之下,發現 Request 的 Content-Type 只有帶 multipart/form-data
    並沒有帶後面的 Boundary,這樣會沒有辦法去 Parse 上傳的檔案或是 text
    那會這樣的原因只會有一個,那就是 API Gateway 對我的 Headers 做了手腳
  4. +
  5. 用 nodejs 啟動 server (記得把 body-parser 改成 text 也支援的設定) +在這樣的實驗之下,發現 Request 的 Content-Type 只有帶 multipart/form-data +並沒有帶後面的 Boundary,這樣會沒有辦法去 Parse 上傳的檔案或是 text +那會這樣的原因只會有一個,那就是 API Gateway 對我的 Headers 做了手腳
-

後來的解決方式,是把設定 API Gateway 為 Proxy,就可以讓 bounday 成功 pass 到後端 Server
那這後面就會介紹如何設定 API Gateway (基本上就只有一個地方,Integration Request & Integration Response)

-

Upload

只要把 HTTP Proxy Integration 打勾即可,不用像上一篇要到其他地方做設定

+

後來的解決方式,是把設定 API Gateway 為 Proxy,就可以讓 bounday 成功 pass 到後端 Server +那這後面就會介紹如何設定 API Gateway (基本上就只有一個地方,Integration Request & Integration Response)

+

Upload

+

只要把 HTTP Proxy Integration 打勾即可,不用像上一篇要到其他地方做設定

-

Server

Upload

1
2
3
4
5
6
7
8
9
10
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
app.use(bodyParser.text({type: '*/*'}))
app.post('/upload_file', (req, res, next) => {
console.log(req.body);
console.log(req.headers);
res.json({})
})
app.listen(8080)
-

DEMO

Upload

從上傳的地方會看到 content-type 最後面會出現 boundary
如果 API Gateway 沒有設成 Proxy 的話,是不會出現

-

不會出現的話,會沒辦法用 content-type 後面的 boundary 去 parse 檔案的
因為檔案之間會用 boundary 去區分,沒了這個就沒辦法識別傳了什麼上來

+

Server

+

Upload

+

1
2
3
4
5
6
7
8
9
10
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
app.use(bodyParser.text({type: '*/*'}))
app.post('/upload_file', (req, res, next) => {
console.log(req.body);
console.log(req.headers);
res.json({})
})
app.listen(8080)

+

DEMO

+

Upload

+

從上傳的地方會看到 content-type 最後面會出現 boundary +如果 API Gateway 沒有設成 Proxy 的話,是不會出現

+

不會出現的話,會沒辦法用 content-type 後面的 boundary 去 parse 檔案的 +因為檔案之間會用 boundary 去區分,沒了這個就沒辦法識別傳了什麼上來

diff --git a/2017/12/11/express-static/index.html b/2017/12/11/express-static/index.html index 197b2dc2c..7e3305f43 100644 --- a/2017/12/11/express-static/index.html +++ b/2017/12/11/express-static/index.html @@ -19,12 +19,12 @@ - + - + @@ -174,10 +174,20 @@

-

前言

最近突然有一個想法開始研究起瀏覽器端的 Cache 方法
加上小弟常用 nodejs + express 去寫前後端
於是開始研究起 express 裡面有一個 middleware 怎麼做起瀏覽器 cache 這件事

- -

介紹

在 express 裡面有一個 function 叫做 express.static()
這個是一個 middleware,最常被用在要讀取一些靜態檔案上面
以這個寫法來說 app.use(express.static(__dirname + './public'))
是指向 public 這個資校夾裡面,假設裡面有一個檔案叫做 index.html 的話,並且伺服器的 port 是 8080
那麼在網址列輸入 http://localhost:8080/index.html 這樣就可以讀到這個檔案了

-

追朔源頭

那我的疑問來了,我打開 Chrome Inspect 的 Network Tab 去看了一下他的 Response Headers
發現一件很奇怪的事情,我明明什麼都沒有設定,卻出現幾個有關 Cache 的 Headers

+

前言

+

最近突然有一個想法開始研究起瀏覽器端的 Cache 方法 +加上小弟常用 nodejs + express 去寫前後端 +於是開始研究起 express 裡面有一個 middleware 怎麼做起瀏覽器 cache 這件事

+

<!-- more -->

+

介紹

+

在 express 裡面有一個 function 叫做 express.static() +這個是一個 middleware,最常被用在要讀取一些靜態檔案上面 +以這個寫法來說 app.use(express.static(__dirname + './public')) +是指向 public 這個資校夾裡面,假設裡面有一個檔案叫做 index.html 的話,並且伺服器的 port 是 8080 +那麼在網址列輸入 http://localhost:8080/index.html 這樣就可以讀到這個檔案了

+

追朔源頭

+

那我的疑問來了,我打開 Chrome Inspect 的 Network Tab 去看了一下他的 Response Headers +發現一件很奇怪的事情,我明明什麼都沒有設定,卻出現幾個有關 Cache 的 Headers

  1. Accept-Ranges
  2. Cache-Control
  3. @@ -186,24 +196,42 @@

    追朔源頭

    那我的疑問來了,

-

有關 Cache 的一些機制和理論就不多作介紹
這裡單純就爬一下 Source Code,看看 express 對靜態檔案做了什麼

+

有關 Cache 的一些機制和理論就不多作介紹 +這裡單純就爬一下 Source Code,看看 express 對靜態檔案做了什麼

-

express

在 express source code 中,發現他是用了另一個 library server-static
於在就再來看看 server-static 做了什麼

-
1
exports.static = require('serve-static');
-

serve-static

我只列出關鍵幾行,其他行主要都是設置參數用而已

-

從第一行可以看出,把 serverStatic 這個 function 給 export 出來了
再往下看會發現有一個 send function 把 path 傳了進去
然後在最後面,stream.pipe(res) 對 response 做了一些更動

+

express

+

在 express source code 中,發現他是用了另一個 library server-static +於在就再來看看 server-static 做了什麼

+

1
exports.static = require('serve-static');

+

serve-static

+

我只列出關鍵幾行,其他行主要都是設置參數用而已

+

從第一行可以看出,把 serverStatic 這個 function 給 export 出來了 +再往下看會發現有一個 send function 把 path 傳了進去 +然後在最後面,stream.pipe(res) 對 response 做了一些更動

於是再往下找找看 send() 這個是什麼東西

-
1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = serveStatic;

var send = require('send')

function serveStatic (root, options) {
// Some codes ....

var stream = send(req, path, opts)
// Some codes ....

// pipe
stream.pipe(res)
}
-

send – send

根據上一段程式最後一段 (12行),他 call 了一個 pipe 的 function
pipe function 裡面去 call this.sendFile(path)
this.sendFile 裡面又去 call self.send(path, stat)
然後在 send 這個 fucntion 裡面出現關鍵的 functionthis.setHeader
看來 response headers 就是在這邊被更改了

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

module.exports = send

// 這邊回傳給到 server-static 去 call
// 也就是上一段程式碼的第 8 行,然後在第
function send (req, path, options) {
return new SendStream(req, path, options)
}

SendStream.prototype.pipe = function pipe (res) {
this.sendFile(path)
}

SendStream.prototype.sendFile = function sendFile (path) {
// 這個等等 demo 截圖會看到,所以先留著
debug('stat "%s"', path)
self.send(path, stat)
}


SendStream.prototype.send = function send (path, stat) {
// 這個等等 demo 截圖會看到,所以先留著
debug('pipe "%s"', path)

// set header fields
this.setHeader(path, stat)
}
-

send – setHeader

找到了對 header 做更動的地方後,以第 11 ~ 20 行中間這段 Code 來說
去設置了 Cache-Control 的內容,依照整個邏輯下如果沒有特別設置,那麼 header 會長以下這樣

+

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = serveStatic;

var send = require('send')

function serveStatic (root, options) {
// Some codes ....

var stream = send(req, path, opts)
// Some codes ....

// pipe
stream.pipe(res)
}

+

send -- send

+

根據上一段程式最後一段 (12行),他 call 了一個 pipe 的 function +pipe function 裡面去 call this.sendFile(path) +this.sendFile 裡面又去 call self.send(path, stat) +然後在 send 這個 fucntion 裡面出現關鍵的 function -- this.setHeader +看來 response headers 就是在這邊被更改了

+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

module.exports = send

// 這邊回傳給到 server-static 去 call
// 也就是上一段程式碼的第 8 行,然後在第
function send (req, path, options) {
return new SendStream(req, path, options)
}

SendStream.prototype.pipe = function pipe (res) {
this.sendFile(path)
}

SendStream.prototype.sendFile = function sendFile (path) {
// 這個等等 demo 截圖會看到,所以先留著
debug('stat "%s"', path)
self.send(path, stat)
}


SendStream.prototype.send = function send (path, stat) {
// 這個等等 demo 截圖會看到,所以先留著
debug('pipe "%s"', path)

// set header fields
this.setHeader(path, stat)
}

+

send -- setHeader

+

找到了對 header 做更動的地方後,以第 11 ~ 20 行中間這段 Code 來說 +去設置了 Cache-Control 的內容,依照整個邏輯下如果沒有特別設置,那麼 header 會長以下這樣

Cache-Control: public, max-age=0

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
SendStream.prototype.setHeader = function setHeader (path, stat) {
var res = this.res

this.emit('headers', res, path, stat)

if (this._acceptRanges && !res.getHeader('Accept-Ranges')) {
debug('accept ranges')
res.setHeader('Accept-Ranges', 'bytes')
}

if (this._cacheControl && !res.getHeader('Cache-Control')) {
var cacheControl = 'public, max-age=' + Math.floor(this._maxage / 1000)

if (this._immutable) {
cacheControl += ', immutable'
}

debug('cache-control %s', cacheControl)
res.setHeader('Cache-Control', cacheControl)
}

if (this._lastModified && !res.getHeader('Last-Modified')) {
var modified = stat.mtime.toUTCString()
debug('modified %s', modified)
res.setHeader('Last-Modified', modified)
}

if (this._etag && !res.getHeader('ETag')) {
var val = etag(stat)
debug('etag %s', val)
res.setHeader('ETag', val)
}
}
-

DEMO

另外提供另個方法可以追回去 (我是懶得寫程式直接看 source code XD)
安裝完環境之後要跑 server 的時候,可以這樣下

+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
SendStream.prototype.setHeader = function setHeader (path, stat) {
var res = this.res

this.emit('headers', res, path, stat)

if (this._acceptRanges && !res.getHeader('Accept-Ranges')) {
debug('accept ranges')
res.setHeader('Accept-Ranges', 'bytes')
}

if (this._cacheControl && !res.getHeader('Cache-Control')) {
var cacheControl = 'public, max-age=' + Math.floor(this._maxage / 1000)

if (this._immutable) {
cacheControl += ', immutable'
}

debug('cache-control %s', cacheControl)
res.setHeader('Cache-Control', cacheControl)
}

if (this._lastModified && !res.getHeader('Last-Modified')) {
var modified = stat.mtime.toUTCString()
debug('modified %s', modified)
res.setHeader('Last-Modified', modified)
}

if (this._etag && !res.getHeader('ETag')) {
var val = etag(stat)
debug('etag %s', val)
res.setHeader('ETag', val)
}
}

+

DEMO

+

另外提供另個方法可以追回去 (我是懶得寫程式直接看 source code XD) +安裝完環境之後要跑 server 的時候,可以這樣下

DEBUG=* node server.js

-

從圖片中可以發現,那些 log message 是一樣的

+

從圖片中可以發現,那些 log message 是一樣的

-

後記

一直以來以為是 express 的做法讓檔案可以 cache 住
原來一直都是默默無名的 opensouce library 在幫助 express 啊
希望這篇有稍微幫助到對 express 處理 static files 有疑慮的人

+

後記

+

一直以來以為是 express 的做法讓檔案可以 cache 住 +原來一直都是默默無名的 opensouce library 在幫助 express 啊 +希望這篇有稍微幫助到對 express 處理 static files 有疑慮的人

@@ -348,11 +376,11 @@