express.js - body-parser

Commit

npm install body-parser

code
var express = require('express');
var bodyParser = require('body-parser')
var app = express()
    .use( bodyParser() ).use(function (req, res) {
        console.log("body", req.body)
        console.log("foo", req.body.foo)
        res.send(req.body)
    })
    .listen(3000);

$ curl -s  http://127.0.0.1:3000/  -H "content-type: application/json" -d  "{\"foo\":123}"
{"foo":123}







express.js - serve-index - list files

  • Commit

  • install serve-index
serve-index (npm install serve-index)

  • list files 
var express = require('express');
var serveIndex = require('serve-index')
var app = express()
    .use( express.static(__dirname + '/public') ) // won't show serveIndex if specifying index files
    .use( serveIndex(__dirname + '/public', {}) )
    .listen(3000);

  • curl 
$ curl http://localhost:3000
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--      0<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8'>
    <meta name="viewport" content="width=device-width, initial-scale=1.0,  maximum-scale=1.0, user-scalable=no" />
    <title>listing directory /</title>
    <style>* {
  margin: 0;
  padding: 0;
  outline: 0;
}
body {
  padding: 80px 100px;
  font: 13px "Helvetica Neue", "Lucida Grande", "Arial";
  background: #ECE9E9 -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff),  to(#ECE9E9));
  background: #ECE9E9 -moz-linear-gradient(top, #fff, #ECE9E9);
  background-repeat: no-repeat;
  color: #555;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3 {
  font-size: 22px;
  color: #343434;
}
h1 em, h2 em {
  padding: 0 5px;
  font-weight: normal;
}
h1 {
  font-size: 60px;
}
h2 {
  margin-top: 10px;
}
h3 {
  margin: 5px 0 10px 0;
  padding-bottom: 5px;
  border-bottom: 1px solid #eee;
  font-size: 18px;
}
ul li {
  list-style: none;
}
ul li:hover {
  cursor: pointer;
  color: #2e2e2e;
}
ul li .path {
  padding-left: 5px;
  font-weight: bold;
}
ul li .line {
  padding-right: 5px;
  font-style: italic;
}
ul li:first-child .path {
  padding-left: 0;
}
p {
  line-height: 1.5;
}
a {
  color: #555;
  text-decoration: none;
}
a:hover {
  color: #303030;
}
#stacktrace {
  margin-top: 15px;
}
.directory h1 {
  margin-bottom: 15px;
  font-size: 18px;
}
ul#files {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
ul#files li {
  float: left;
  width: 30%;
  line-height: 25px;
  margin: 1px;
}
ul#files li a {
  display: block;
  height: 25px;
  border: 1px solid transparent;
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;
  overflow: hidden;
  white-space: nowrap;
}
ul#files li a:focus,
ul#files li a:hover {
  background: rgba(255,255,255,0.65);
  border: 1px solid #ececec;
}
ul#files li a.highlight {
  -webkit-transition: background .4s ease-in-out;
  background: #ffff4f;
  border-color: #E9DC51;
}
#search {
  display: block;
  position: fixed;
  top: 20px;
  right: 20px;
  width: 90px;
  -webkit-transition: width ease 0.2s, opacity ease 0.4s;
  -moz-transition: width ease 0.2s, opacity ease 0.4s;
  -webkit-border-radius: 32px;
  -moz-border-radius: 32px;
  -webkit-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px  rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03);
  -moz-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px  rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03);
  -webkit-font-smoothing: antialiased;
  text-align: left;
  font: 13px "Helvetica Neue", Arial, sans-serif;
  padding: 4px 10px;
  border: none;
  background: transparent;
  margin-bottom: 0;
  outline: none;
  opacity: 0.7;
  color: #888;
}
#search:focus {
  width: 120px;
  opacity: 1.0;
}
/*views*/
#files span {
  display: inline-block;
  overflow: hidden;
  text-overflow: ellipsis;
  text-indent: 10px;
}
#files .name {
  background-repeat: no-repeat;
}
#files .icon .name {
  text-indent: 28px;
}
/*tiles*/
.view-tiles .name {
  width: 100%;
  background-position: 8px 5px;
}
.view-tiles .size,
.view-tiles .date {
  display: none;
}
/*details*/
ul#files.view-details li {
  float: none;
  display: block;
  width: 90%;
}
ul#files.view-details li.header {
  height: 25px;
  background: #000;
  color: #fff;
  font-weight: bold;
}
.view-details .header {
  border-radius: 5px;
}
.view-details .name {
  width: 60%;
  background-position: 8px 5px;
}
.view-details .size {
  width: 10%;
}
.view-details .date {
  width: 30%;
}
.view-details .size,
.view-details .date {
  text-align: right;
  direction: rtl;
}
/*mobile*/
@media (max-width: 768px) {
  body {
    font-size: 13px;
    line-height: 16px;
    padding: 0;
  }
  #search {
    position: static;
    width: 100%;
    font-size: 2em;
    line-height: 1.8em;
    text-indent: 10px;
    border: 0;
    border-radius: 0;
    padding: 10px 0;
    margin: 0;
  }
  #search:focus {
    width: 100%;
    border: 0;
    opacity: 1;
  }
  .directory h1 {
    font-size: 2em;
    line-height: 1.5em;
    color: #fff;
    background: #000;
    padding: 15px 10px;
    margin: 0;
  }
  ul#files {
100  7241  100  7241    0     0  1010k      0 --:--:-- --:--:-- --:--:-- 1178k    border-top: 1px solid #cacaca;
  }
  ul#files li {
    float: none;
    width: auto !important;
    display: block;
    border-bottom: 1px solid #cacaca;
    font-size: 2em;
    line-height: 1.2em;
    text-indent: 0;
    margin: 0;
  }
  ul#files li:nth-child(odd) {
    background: #e0e0e0;
  }
  ul#files li a {
    height: auto;
    border: 0;
    border-radius: 0;
    padding: 15px 10px;
  }
  ul#files li a:focus,
  ul#files li a:hover {
    border: 0;
  }
  #files .header,
  #files .size,
  #files .date {
    display: none !important;
  }
  #files .name {
    float: none;
    display: inline-block;
    width: 100%;
    text-indent: 0;
    background-position: 0 50%;
  }
  #files .icon .name {
    text-indent: 41px;
  }
}
</style>
    <script>
      function $(id){
        var el = 'string' == typeof id
          ? document.getElementById(id)
          : id;
        el.on = function(event, fn){
          if ('content loaded' == event) {
            event = window.attachEvent ? "load" : "DOMContentLoaded";
          }
          el.addEventListener
            ? el.addEventListener(event, fn, false)
            : el.attachEvent("on" + event, fn);
        };
        el.all = function(selector){
          return $(el.querySelectorAll(selector));
        };
        el.each = function(fn){
          for (var i = 0, len = el.length; i < len; ++i) {
            fn($(el[i]), i);
          }
        };
        el.getClasses = function(){
          return this.getAttribute('class').split(/\s+/);
        };
        el.addClass = function(name){
          var classes = this.getAttribute('class');
          el.setAttribute('class', classes
            ? classes + ' ' + name
            : name);
        };
        el.removeClass = function(name){
          var classes = this.getClasses().filter(function(curr){
            return curr != name;
          });
          this.setAttribute('class', classes.join(' '));
        };
        return el;
      }
      function search() {
        var str = $('search').value.toLowerCase();
        var links = $('files').all('a');
        links.each(function(link){
          var text = link.textContent.toLowerCase();
          if ('..' == text) return;
          if (str.length && ~text.indexOf(str)) {
            link.addClass('highlight');
          } else {
            link.removeClass('highlight');
          }
        });
      }
      $(window).on('content loaded', function(){
        $('search').on('keyup', search);
      });
    </script>
  </head>
  <body class="directory">
    <input id="search" type="text" placeholder="Search" autocomplete="off" />
    <div id="wrapper">
      <h1><a href="/">~</a> / </h1>
      <ul id="files" class="view-tiles"><li><a href="/lalala.html" class=""  title="lalala.html"><span class="name">lalala.html</span><span  class="size">168</span><span class="date">2020-7-29 1:04:56 ├F10:  AM┤</span></a></li>
<li><a href="/test.html" class="" title="test.html"><span  class="name">test.html</span><span class="size">147</span><span  class="date">2020-7-24 1:46:51 ├F10: AM┤</span></a></li></ul>
    </div>
  </body>
</html>


express.js - express.static - index file

  • Commit


  • use express.static
var express = require('express');  
var app = express()  
            .use(  express.static(__dirname + '/public')  )  
            .listen(3000);


express.js - serveStatic - index file

  • specufy in the second argument
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Index - lalala</title>
</head>
<body>
This is index page - lalala
</body>
</html>

var express = require('express');
var serveStatic =  require('serve-static');
var app = express().use(  serveStatic(__dirname + '/public', {'index': ['lalala.html']})  ).listen(3000);

$curl http://localhost:3000
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Index - lalala</title>
</head>
<body>
This is index page - lalala
</body>
</html>

express.js - helloworld

Commit

npm install
npm install express
npm install serve-static

public file: public/test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Helloworld</title>
</head>
<body>
Helloworld
</body>
</html>

app.js
var express = require('express');
var serveStatic =  require('serve-static');
var app = express().use(  serveStatic(__dirname + '/public')  ).listen(3000);

Run
node app.js

Browse
$ curl http://localhost:3000/test.html
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   147  100   147    0     0  29400      0 --:--:-- --:--:-- --:--:--  36750<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Helloworld</title>
</head>
<body>
Helloworld
</body>
</html>





inquirer - mark todo item done

By ‘checkbox’ type can choose items

Commit

Code
import * as inquirer from "inquirer"

// Object.values(Commands) will become [List, Quit, 1, 2] without specifying string value
enum Commands {
    Add = "Add",
    Done = "Done",
    Quit = "Quit"
}

let todo: Array<Task> = []

promptList()

class Task {
    done: boolean = false;
    constructor(public name:string) {
    }
    markDone() {
        this.done = true
    }
    toString = ():string => {
        return this.name + "=>" + (this.done ? "done" : "not done")
    }
}

function listTodo() {
    todo.forEach(item => {
        console.log(`${item}`)
    })
}

function promptDone() {
    inquirer.prompt({
        type: "checkbox",
        name: "check",
        message: "Mark done:",
        choices: todo.map(item => ({
            name: item.name,
            value: item.name,
            checked: item.done
        }))
    }).then(answers => {
        console.log(answers)
        let checkedItems = answers['check'] as string[]
        if (answers['check'] != "") {
            todo.forEach(item => item.done = false)
            todo.forEach(item => {
                checkedItems.forEach(checkedItem => {
                    if (item.name == checkedItem) {
                        item.done = true
                    }
                })
            })
        }
        promptList();
    })
}

function promptAdd() {
    inquirer.prompt({
        type: "input",
        name: "add",
        message: "Input TODO:",
    }).then(answers => {
        if (answers['add'] != "") {
            todo.push(new Task(answers['add']))
        }
        promptList();
    })
}

function promptList() {
    console.clear();
    listTodo();
    inquirer.prompt({
        type: "list",
        name: "command",
        message: "Choose option",
        choices: Object.values(Commands)
    }).then(answers => {
        switch (answers["command"]) {
            case Commands.Add:
                promptAdd();
                break;
            case Commands.Done:
                promptDone();
                break;
            case Commands.Quit:
                console.log("Select Quit")
                break;
            default:
                console.log("Missing " + answers["command"])
        }
    })
}

Inquirer - add item to list

Use "input" type in inquirer can support input information

Commit

Code
import * as inquirer from "inquirer"

// Object.values(Commands) will become [List, Quit, 1, 2] without specifying string value
enum Commands {
    AddTodo = "AddTodo",
    Quit = "Quit"
}

promptList()

var todo: Array<string> = []

function listTodo() {
    console.log(todo)
}

function promptAdd() {
    inquirer.prompt({
        type: "input",
        name: "add",
        message: "Input TODO:",
    }).then(answers => {
        if (answers['add'] != "") {
            todo.push(answers['add'])
        }
        promptList();
    })
}

function promptList() {
    console.clear();
    listTodo();
    inquirer.prompt({
        type: "list",
        name: "command",
        message: "Choose option",
        choices: Object.values(Commands)
    }).then(answers => {
        switch (answers["command"]) {
            case Commands.AddTodo:
                promptAdd();
                break;
            case Commands.Quit:
                console.log("Select Quit")
                break;
            default:
                console.log("Missing " + answers["command"])
        }
    })
}




Convert mov to gif in Mac

Motivation
File extension of screen record in Mac is .mov (By QuickTime)
Can use ffmpeg to convert to gif to reduce file size

Install ffmpeg
brew install ffmpeg

Convert mov to gif
ffmpeg -i AM.mov output.gif

Reference

inquirer - simple list and quit command

Git Repository

Install libraries
npm install inquirer
npm install typescript

List and Quit commands
import * as inquirer from "inquirer"


// Object.values(Commands) will become [List, Quit, 1, 2] without specifying string value
enum Commands {
    List = "List",
    Quit = "Quit"
}


inquirer.prompt({
    type: "list",
    name: "command",
    message: "Choose option",
    choices: Object.values(Commands)
}).then(answers => {
    switch (answers["command"]) {
        case Commands.List:
            console.log("Select List")
            break;
        case Commands.Quit:
            console.log("Select Quit")
            break;
        default:
            console.log("Missing " + answers["command"])
    }
})

Config Wireguard

Wireguard
Wireguard is a VPN software, which is included in Linux 5.6 kernel 

Install ubuntu 18.04
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install openssh-server

$ sudo add-apt-repository ppa:wireguard/wireguard
$ sudo apt-get update
$ sudo apt-get install wireguard

Open /etc/gai.conf
Uncomment following line
#
# For sites which prefer IPv4 connections change the last line to
#
precedence ::ffff:0:0/96 100

Enable ip forward in server and reboot, so that packet can be forwarded from default gateway to other interface with same subnet
echo "net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1" > /etc/sysctl.d/wg.conf

Gen key for server and client
$ umask 077
$ sudo wg genkey > private
$ sudo wg pubkey < private > public

Deploy server config
File: /etc/wireguard/wg0.conf
MASQUERADE: packet’s ip header will be changed to private ip and restore to public ip when writing back
10.0.0.1 can be freely configured, only need to make sure peers are in the same subnet
[Interface]
Address = 10.0.0.1/24
#SaveConfig = true
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT && iptables -t nat -A POSTROUTING -o enp0s3 -j MASQUERADE && iptables -A INPUT -i wg0 -p udp --dport 51820 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT && iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE && iptables -D INPUT -i wg0 -p udp --dport 51820 -j ACCEPT
ListenPort = 51820
PrivateKey = aP7Y6f0ubHbweFSs5EouXsT+klvsp2iFRZsmuBz+IHQ=

[Peer]
PublicKey = utH967EMNmx3Of9Breqp27T8+ZCOs1nawsmk+HpCLCY=
AllowedIPs = 10.0.0.2/32

Deploy client config
File: /etc/wireguard/client.conf
[Interface]
Address = 10.0.0.2/24
PrivateKey = WHLj16xU6/dq59Qks8Zn14vCjk3PMc7o4Pjm6lktfmE=
DNS = 1.1.1.1

[Peer]
PublicKey = 3GODl2zWseKTpRRiArn00TEZHw9qs0oOxD1AF4gcv3c=
AllowedIPs = 0.0.0.0/0
Endpoint = 10.247.33.177:51820

Gen QRCode
$ sudo apt install qrencode
$ qrencode -t ansiutf8 < /etc/wireguard/client.conf

Start server
$ sudo wg-quick up wg0

Start client
$ sudo wg-quick up client

Wireguard do handshake through UDP protocol, so client connect successfully doesn’t mean VPN connection work.
Can debug by ip ping, route, traceroute commands to make sure peers can be connected.

別名演算法 Alias Method

 題目 每個伺服器支援不同的 TPM (transaction per minute) 當 request 來的時候, 系統需要馬上根據 TPM 的能力隨機找到一個適合的 server. 雖然稱為 "隨機", 但還是需要有 TPM 作為權重. 解法 別名演算法...