2017/12/24

tmux

Common Shortcuts

test

# start new session
$ tmux new -s myname

# list sessions
$ tmux ls

# attach to session
$ tmux a -t 0

# rename session
$ tmux rename-session -t 0 NEW_NAME

# kill session
$ tmux kill-session -t 0

Windows Control

  • <prefix> c create window
  • <prefix> w list windows
  • <prefix> n next window
  • <prefix> p previous window
  • <prefix> f find window
  • <prefix> , name window
  • <prefix> & kill window
  • <prefix> [ check history

Pane Control

  • <prefix> % vertical split
  • <prefix> " horizontal split
  • <prefix> o swap panes
  • <prefix> q show pane numbers
  • <prefix> x kill pane
  • <prefix> q (Show pane numbers, when the numbers show up type the key to goto that pane)
  • <prefix> { (Move the current pane left)
  • <prefix> } (Move the current pane right)
  • <prefix> z toggle pane zoom

Resize Pane

  • <prefix> : resize-pane -D (Resizes the current pane down)
  • <prefix> : resize-pane -U (Resizes the current pane upward)
  • <prefix> : resize-pane -L (Resizes the current pane left)
  • <prefix> : resize-pane -R (Resizes the current pane right)
  • <prefix> : resize-pane -D 20 (Resizes the current pane down by 20 cells)
  • <prefix> : resize-pane -U 20 (Resizes the current pane upward by 20 cells)
  • <prefix> : resize-pane -L 20 (Resizes the current pane left by 20 cells)
  • <prefix> : resize-pane -R 20 (Resizes the current pane right by 20 cells)
  • <prefix> : resize-pane -t 2 20 (Resizes the pane with the id of 2 down by 20 cells)
  • <prefix> : resize-pane -t -L 20 (Resizes the pane with the id of 2 left by 20 cells)

Start With Configuration

#!/bin/bash
tmux -2 new-session \; \
send-keys 'cd /var/www/test; vim' C-m \; \
split-window -h \; \
send-keys 'top' C-m \; \
resize-pane -R 30 \; \
split-window -v \; \
send-keys 'while true; do date; sleep 1; done' C-m \; \

My Config

# ~/.tmux.conf
set -g default-terminal "xterm"
setw -g mode-keys vi

# ~/.bashrc
alias tmux='tmux -2'

Auto Start Config

#!/bin/sh

SERVICE=apache_watcher

tmux has-session -t $SERVICE

if [[ $? != 0 ]]; then
	tmux new -s $SERVICE -d

	tmux split-window -h
	tmux split-window -v -t $SERVICE:0.1
	tmux split-window -v -t $SERVICE:0.0

	tmux send-keys -t $SERVICE:0.0 'htop'  C-m
	tmux send-keys -t $SERVICE:0.2 'nload' C-m
	tmux send-keys -t $SERVICE:0.3 'sudo apachetop' C-m

	tmux clock-mode -t $SERVICE:0.1
	tmux resize-pane -R 30
fi

tmux a -t $SERVICE

Reference Site

https://gist.github.com/MohamedAlaa/2961058
https://blog.htbaa.com/news/tmux-scripting

2017/12/18

Simple Regular Expression In PHP, Python, Node.js

今天需要用正規式找出符合 xxx[yyy] 後面配任意內容的結果,紀錄一下三種語言怎麼操作正規式。

PHP
<?php

$pattern = '/\w+\[\w+\].*/i';
$strings = [
    'aaa[bbb]',
    '1234[bbb][cccc]',
    '[111][22]',
    'dd[gg]ff',
    'aa[',
    'aaa][bbbb',
    'aaabbbccc',
];

foreach ($strings as $s) {
    var_dump($s.' is '.preg_match($pattern, $s));
}
Python
import re

pattern = r'\w+\[\w+\].*';
strings = [
    'aaa[bbb]',
    '1234[bbb][cccc]',
    '[111][22]',
    'dd[gg]ff',
    'aa[',
    'aaa][bbbb',
    'aaabbbccc'
];

for s in strings:
    print('%s is %s' % (s, True if re.match(pattern, s, re.IGNORECASE) else False))
Node.js
const pattern = /\w+\[\w+\].*/i;
const strings = [
    'aaa[bbb]',
    '1234[bbb][cccc]',
    '[111][22]',
    'dd[gg]ff',
    'aa[',
    'aaa][bbbb',
    'aaabbbccc'
];

for (v of strings) {
    console.log(v, 'is', pattern.test(v));
}

2017/11/06

crawler in PHP, Python, Node.js

網頁爬蟲是滿常遇到的課題,今天練習一下用這三種語言寫爬蟲

PHP

sunra/php-simple-html-dom-parser

crawler.php
<?php

include 'vendor/autoload.php';

use Sunra\PhpSimple\HtmlDomParser;

$url = 'https://www.ptt.cc/bbs/NBA/index.html';
$html = HtmlDomParser::file_get_html($url);
$titles = $html->find('div.title');
$file = fopen('php.txt', 'w');

foreach ($titles as $title) {
    $subject = trim($title->plaintext)."\n";
    fwrite($file, $subject);
}

fclose($file);
$html->clear();

Python

crawler.py
# -*- coding: utf-8 -*-

import sys
import requests
from bs4 import BeautifulSoup

reload(sys)
sys.setdefaultencoding('utf-8')

url = 'https://www.ptt.cc/bbs/NBA/index.html'
res = requests.get(url)
soup = BeautifulSoup(res.text, 'html.parser')
titles = soup.find_all('div', 'title')
f = open('py.txt', 'w');

for title in titles:
    f.write('%s\n' % title.text.strip())

f.close()

Node.js

crawler.js
const cheerio = require('cheerio');
const request = require('request');
const fs = require('fs');

const url = 'https://www.ptt.cc/bbs/NBA/index.html';
const out = fs.createWriteStream('nodejs.txt');

request(url, (err, res, body) => {
    if (err) {
        console.log(err);
    }

    const $ = cheerio.load(body);
    const divs = $('div.title');

    divs.each((i, item) => {
        out.write($(item).text().trim() + '\n');
    });

    out.end();
});

這三個程式執行的結果會得到一樣的內容,當然這是很簡略的範例,照理說要寫一些容錯判斷,但練習用的範例先這樣吧,再附上執行結果比較一下速度

PHP 7.0.22
$ time php crawler.php

real    0m0.614s
user    0m0.072s
sys     0m0.032s

real    0m0.570s
user    0m0.056s
sys     0m0.032s

real    0m0.570s
user    0m0.056s
sys     0m0.032s

real    0m0.515s
user    0m0.040s
sys     0m0.032s

real    0m0.515s
user    0m0.040s
sys     0m0.032s

real    0m0.515s
user    0m0.040s
sys     0m0.032s
Node.js v9.0.0
$ time node crawler.js

real    0m1.357s
user    0m0.244s
sys     0m0.396s

real    0m1.744s
user    0m0.376s
sys     0m0.312s

real    0m1.312s
user    0m0.272s
sys     0m0.380s

real    0m1.522s
user    0m0.348s
sys     0m0.304s

real    0m1.369s
user    0m0.288s
sys     0m0.396s

real    0m1.523s
user    0m0.388s
sys     0m0.344s
Python 2.7.12
$ time python crawler.py
real    0m0.666s
user    0m0.232s
sys     0m0.064s

real    0m0.682s
user    0m0.204s
sys     0m0.088s

real    0m0.709s
user    0m0.240s
sys     0m0.064s

real    0m0.624s
user    0m0.228s
sys     0m0.032s

real    0m0.669s
user    0m0.264s
sys     0m0.052s

real    0m0.698s
user    0m0.260s
sys     0m0.068s

基本上 PHP 跟 Python 都是按下去一下就做完了,Node.js 有等待的感覺,不知道是哪個環節出問題,我有把檔案寫入跟 dom 搜尋的部份關閉,速度沒差多少,所以是 [request][] 套件慢?但網路大家都推 [request][],他應該也是基於 node.js 內建的 http module 上的高級封裝,而且其他人也都有用套件,只能說幾乎我每次測試東西 node.js 數據都會吊車尾,目前還是選擇使用他好用的工具群為主。

2017/10/20

解構賦值比較

PHP 有一個可以替換變數的 function 叫 list,使用範例如下

test.php
$arrs = ['chan', 37];
list($name, $age) = $arrs;
var_dump($name, $age);
$ php test.php
string(4) "chan"
int(37)

在 node.js 的世界的話,用法是這樣的

test.js
const arrs = ['chan', 37];
const [name, age] = arrs;
console.log(name, age);
$ node test.js
chan 37

python 的話超簡單

test.py
arrs = ['chan', 37]
name, age = arrs
print(name, age)
$ python test.py
('chan', 37)

在 PHP 以及 js 的世界其實我自己用的不多,其他人的範例 code 也不算到常見,但在 python 的世界出現的頻率還算不低

2017/10/19

動態呼叫 module - python

在寫程式上我們會常遇到使用不同的參數動態的呼叫他的 class,在 PHP 會使用 call_user_funcall_user_func_array 來達到這個目的,而 node.js 載入 module 本來就是可以使用變數所以相對簡單,舉例來說

tester.js
module.exports = {
    test() {
        console.log('test');
    }
};
main.js
moduleName = 'tester';
const tester = require(`./${moduleName}`);

tester.test();
$ node main.js
test

不過 python 載入模組的方式無法使用字串,但可以透過 __import__ 以及 getattr 這兩個方式組合達到我們要的目的,檔案的結構如下:

transportation/
	__init__.py
	base.py
	bike.py
	car.py
	main.py
base.py
class Base(object):
    def go(self):
        print('go with {excute_name}'.format(excute_name=self.excute_name))

這隻 module 簡單來講就是 car 以及 bike 共同繼承的工具,目的在測試這樣的載入方式使用繼承會不會有問題

car.py
from base import Base

class Car(Base):
    excute_name = 'car';

基本上只是初始化一個 module,放一個 property

bike.py
from base import Base

class Bike(Base):
    excute_name = 'bike'

同上

main.py
# -*- coding: utf-8 -*-

transportations = [
    'car',
    'nope',
    'bike'
]

for transportation in transportations:
    class_name = transportation.capitalize() # 字首改大寫即為 class name
    module_name = 'transportation.{name}'.format(name=transportation)

    try:
        module = __import__(module_name, globals(), locals(), [class_name])
        collector = getattr(module, class_name)()
        collector.go()
    except Exception as e:
        print(str(e))

我們把要呼叫的 module 寫成一個 array 跑迴圈,中間我故意放了一個不存在的 module,程式碼裡面有使用 try exception,所以不用怕出錯會中斷,執行程式結果如下

$ python main.py
go with car
No module named nope
go with bike

2017/08/07

Prompt In All Language

最近喜歡刻一些互動的小工具,程式碼將需求分析後在 terminal 與 user 互動,confirm 內容後動作是常見的,這邊紀錄一下三個語言的 prompt 方法

prompt.php
echo 'Are you sure? [y/n]: ';

$handle = fopen('php://stdin', 'r');
$answer = trim(fgets($handle));

if ($answer === 'y') {
    echo 'Yes!'.PHP_EOL;
} else {
    echo 'Canceled!'.PHP_EOL;
}

fclose($handle);
prompt.js
const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.question('Are you sure? [y/n]: ', (answer) => {
    if (answer.trim() === 'y') {
        console.log('Yes!');
    } else {
        console.log('Canceled!');
    }

    process.exit();
});
prompt.sh
#!/bin/bash

read -p 'Are you sure? [y/n]: ' answer

if [[ $answer == 'y' ]]; then
    echo "Yes!"
else
    echo "Canceled!"
fi

2017/07/25

Express MVC

最近心血來潮又把 express 玩了一下,寫篇文章來紀錄一下整個過程,會用到的工具如下:

  1. express 4
  2. node-orm2
  3. nodemon

首先安裝 express-generator,這是一個可以自動產生 express 需要內容的工具

$ sudo npm i -g express-generator

安裝好之後,我們切換到我們想要的目錄來產生專案資料夾,我選用 twig 當作我的樣板引擎,我在 PHP 使用 CI 的時候也是選用同一個引擎,可以無痛轉移

/var/www $ sudo express -v twig project

/var/www $ cd project
/var/www/project $ sudo npm i
/var/www/project $ sudo npm i orm mysql --save

專案目錄產生後,我們可以使用 nodemon 來監控目錄,也可以達到修改檔案後自動重起的效果

/var/www/project $ sudo nodemon bin/www

這時候打開 http://10.10.10.16:3000 (我的 vm 測試 IP)就可以看到預設網址,如果希望改 port 的話可以在 env 設定 port,或者直接去改 bin/www 這個檔案

打開 app.js 這是預設的內容

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var index = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'twig');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', index);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

所有的動作都在這隻 app.js 設定,我們來建立一個 product routes 檔案

routes/product.js
const express = require('express');
const router = express.Router();

router.get('/', function(req, res, next) {
    res.render('product/index');
});

module.exports = router;
view/product/index.twig
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h3>This is product page</h3>
</body>
</html>

app.js 加上這個 routes 的設定

app.js
var index = require('./routes/index');
var users = require('./routes/users');
var product = require('./routes/product');
var index = require('./routes/index');
var users = require('./routes/users');
var product = require('./routes/product');

app.use('/', index);
app.use('/users', users);
app.use('/product', product);

這時網址改成 http://10.10.10.16:3000/product,就會看到 This is product page 這幾個字

接下來我們進行資料庫的串連,在 MySQL 內建立一個叫 express 的資料庫,另外建立一個 products 的 table 並且塞入資料

CREATE TABLE `products` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(50) NOT NULL
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_unicode_ci;

接下來我們來設定資料庫連接,因為我不喜歡全部的東西都塞在 app.js,所以我另外開一個資料夾來放設置檔

config/db.js
module.exports = (app, orm) => {
    app.use(orm.express("mysql://root:123456@127.0.0.1/express", {
        define: function(db, models, next) {
            models.product = db.define('products', {
                id: Number,
                name: String
            });
            next();
        }
    }));
};

修改 app.js 啟動這個設定,只要放在 app 以及 orm 初始化後即可

app.js
require('./config/db.js')(app, orm);

我們先將 routes/product.js 修改成以下內容,變可以從網址先看到結果

const express = require('express');
const router = express.Router();

router.get('/', function(req, res, next) {
    req.models.product.all((err, data) => {
        res.json(data);
    })
});

module.exports = router;
http://10.10.10.16:3000/product
[{"id":1,"name":"product a"},{"id":2,"name":"product b"}]

這是最基本的用法,但我覺得這樣可重複利用性太低了,所以我們來建立類似 model 機制吧

routes/product.js
const express = require('express');
const router = express.Router();
const Product = require('../model/product.js');

router.get('/', async(req, res, next) => {
    try {
        const product = new Product(req.models.product);
        res.json(await product.getAllNews());
    } catch (e) {
        res.send(e);
    }
});

module.exports = router;
model/product.js
class Product {
    constructor(model) {
        this.model = model
    }

    async getAllNews() {
        return new Promise((resolve, reject) => {
            this.model.all((err, data) => {
                if (err) {
                    reject(err);
                }

                resolve(data);
            })
        })
    }
}

module.exports = Product;

這樣的執行結果會一模一樣,但把東西拆乾淨模組化了,再修改一次在 view 呈現吧

routes/product.js
const express = require('express');
const router = express.Router();
const Product = require('../model/product.js');

router.get('/', async(req, res, next) => {
    try {
        const product = new Product(req.models.product);
        res.render('product/index', {
            products: await product.getAllNews()
        });
    } catch (e) {
        res.send(e);
    }
});

module.exports = router;
views/product/index.twig
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h3>This is product page</h3>
    {% for product in products %}
    <div>product: {{ product.name }}</div>
    {% endfor %}
</body>
</html>

以上就是這兩天試玩 express 的整理,沒有實現所謂的 controller 部份,就先把 routes 當 controller,當然一定會有更多更好的方法,再慢慢探索,在此也感謝陳默司大大的指導

2017/06/28

Monitor File Change And Execute Code

現在寫 PHP 的時候都會試著在 node.js 以及 shell script 上實現,所以會不斷的嘗試程式碼,每次要看執行結果都要下一次 command 也挺累的,寫了一隻 w.js 來監控這三種檔案的變動並帶入對應的呼叫方式來節省時間

此功能需安裝 watch,執行方法為 node w.js xxx.php

w.js
const watch = require('watch');
const { exec } = require('child_process');
const path = require('path');
const argvs = process.argv;
const target = argvs[2];
const fs = require('fs');
const commandMap = {
    sh: 'bash',
    php: 'php',
    js: 'node'
};

if (target === undefined) {
    console.log('Need file argument!');
    return;
}

if (!fs.existsSync(target)) {
    console.log(`${target} is not exists!`);
    return;
}

let ext = path.extname(target).replace(/\./g, '');

if (Object.keys(commandMap).indexOf(ext) === -1) {
    console.log(`${ext} is not legal!`);
    return;
}

console.log(`Start to watch "${target}"!`);

watch.createMonitor('./', (monitor) => {
    monitor.on('changed', (file) => {
        if (file === target) {
            exec(`clear; ${commandMap[ext]} ${file}`, (error, stdout, stderr) => {
                if (error) {
                    console.error(`exec error: ${error}`);
                    return;
                }

                console.log(stdout);
                console.log(stderr);
            });
        }
    });
});

實際使用畫面大概是這樣

w.js 示意圖

2017/06/27

Random String

公司有一個需求想要產生 unique id,雖然 PHP 有 uniqid 可以使用,但他最基本的長度是 13 個字元,所以決定自己刻一個利用 0-9、a-z 產生的亂數密碼,會有 PHP、nodejs 以及 shell script 版本。

PHP
<?php

function code($length = 10)
{
    $codeString = '0123456789abcdefghijklmnopqrstubwxyz';
    $codeStringSplit = str_split($codeString);
    $codeStringSize = count($codeStringSplit);
    $code = '';

    for ($i = 0; $i < $length; ++$i) {
        $rand = mt_rand(0, $codeStringSize - 1);
        $code .= $codeStringSplit[$rand];
    }

    return $code;
}

for ($i = 0; $i < 10; $i++) {
    echo code().PHP_EOL;
}
node.js
function code(length) {
    var stringLength = length || 10;
    var codeString = '0123456789abcdefghijklmnopqrstubwxyz';
    var codeStringSplit = codeString.split('');
    var codeStringSize = codeStringSplit.length;
    var code = '';
    var min = 0;
    var max = codeStringSize - 1;

    for (var i = 0; i < stringLength; ++i) {
        var rand = Math.floor(Math.random() * (max - min + 1)) + min;

        code += codeStringSplit[rand];
    }

    return code;
}

for (var i = 0; i < 10; ++i) {
    console.log(code());
}
bash
#!/bin/bash

function code() {
    result=""
    length=10
    codeString="0123456789abcdefghijklmnopqrstubwxyz";
    declare -a array

    for (( i = 0; i < ${#codeString}; i++ )); do
        array+=(${codeString:$i:1})
    done

    if [[ -n $1 ]]; then
        declare -i length=$1
    fi

    max=$((${#array[@]} - 1))
    
    for (( i = 0; i < ${length}; i++ )); do
        rand=$(shuf -i 0-${max} -n 1)
        result="${result}${array[${rand}]}"
    done

    echo ${result}
}

for (( j = 0; j < 10; j++ )); do
    code
done

2017/02/27

Reverse Proxy For Docker

前言

現在很流行用 docker 處理一些服務,以我這行來講用來處理 LAMP 或者是 LNMP 最常見,也可能是產生個 gitlab 之類的,docker 最大的優點是服務都跑在 container,安裝快啟動也快,要幾個有幾個,寫好 config 像是 Dockerfile 之類的設定還可以一個指令搞定,讓你的電腦可以保持很乾淨,網站使用 80 port 當作預設的入口,但每個 server port 都是唯一的,所以假設你有很多網站服務,可能會指派很多不同的 port,像是 localhost:66666 或者是 localhost:66667,但有些測試你可能想要盡可能逼真,希望是用網址來跑服務的話,我們可以在 80 port 假一個反向的 proxy server,讓你可以將 domain 指定到帶 port 的 IP,下面會示範 nginx 以及 apache 的設定方式

Docker

我測試的環境是 Windows,我先使用 Vagrant 安裝一個 ubuntu 16 的 VM,IP 為 10.10.10.22,之後將會在這個 VM 裡面跑 nginx 以及 apache servie,還有 docker

我 container 使用的 image 是 novice/lnmp,我先啟動兩個 web service 服務

$ sudo docker run -itd --name web1 -p 33333:80 novice/lnmp
$ sudo docker run -itd --name web2 -p 33334:80 novice/lnmp

我將 33333 port 給 nginx 這個 web service 使用,33334 給 apache 使用,然後我們將 Windows 的 hosts 設定一下

10.10.10.22 docker.web1
10.10.10.22 docker.web2

接著我進到 apache container 的 /var/www 目錄,加上一個 hello.php

<?php

echo 'hello I am web1';

同樣在 nginx 的 container /var/www 加入 hello.php。

<?php

echo 'hello I am web2';

在網址輸入 http://docker.web1:33333/hello.php 以及 http://docker.web2:33334/hello.php 各別會看到 hello I am web1 以及 hello I am web2,不過其實目前兩個網址互換得到的結果也會一樣,因為現在主要是因為不同的 port 帶出不同的 web service,而我們要做的事情就是把 port 拿掉以後還會成立

nginx

先在這個 VM 安裝 nginx。

$ sudo apt-get install nginx

安裝好之後,我們在 /etc/nginx/sites-available的目錄建立一個 proxy.conf內容如下

server {
    listen 80;
    sendfile off;
    server_name docker.web1;

    location / {
        proxy_pass http://127.0.0.1:33333;
        proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
    }
}

server {
    listen 80;
    sendfile off;
    server_name docker.web2;

    location / {
        proxy_pass http://127.0.0.1:33334;
        proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
    }
}

接著設定 config,然後將服務重啟

$ sudo ln -s /etc/nginx/sites-available/proxy.conf /etc/nginx/sites-enabled/proxy.conf
$ sudo service nginx restart

此時如果你將 port 拿掉還是可以看到正確結果的話表示成功了,說明一下 sendfile off 主要是因為我使用 VM,這個不關的話會一直看到 cache,如果你是直接在 Linux 環境的話應該是不用特別設定

Apache

首先來安裝 apache,記得先將 nginx 關掉,並且確認兩個 proxy 必要的 module 有開

$ sudo service nginx stop
$ sudo apt-get install apache2
$ sudo a2enmod proxy
$ sudo a2enmod proxy_http

接著在 /etc/apache2/sites-available加入 proxy.conf,內容如下

<VirtualHost *:80>
    ServerName docker.web1
    ProxyPass / http://127.0.0.1:33334/
    ProxyPassReverse  / http://127.0.0.1:33334/
</VirtualHost>

<VirtualHost *:80>
    ServerName docker.web2
    ProxyPass / http://127.0.0.1:33333/
    ProxyPassReverse  / http://127.0.0.1:33333/
</VirtualHost>

接著我們製作 link,以及重起 apache

$ sudo a2ensite proxy.conf
$ sudo service apache2 restart

如果那兩個網址重整以後一樣可以看到對應的文字,表示 apache reserse proxy 也成功了