Jex’s Note

微信串接事前準備

微信登入

必須先備案, 備案後去申請微信網站應用時還需要附一些公司名稱, 負責人, 章, 備案號等…

列印填完後掃描再上傳 word 審核

微信公眾號

先請代辦幫忙申請公眾號或服務號

公眾號一天只能發一封, 但是是被收在訂閱帳號裡的

服務號一個月只能發4封, 但是是獨立的聊天訊息, 曝光率較高

代辦只會幫你申請, 不含認證, 認證才能用到一些進階的功能, 例如取得服務號裡的獲取用戶基本訊息, 獲取關注者列表等..

— 申請成功, 未認證成功只能用簡單功能, 例如 接發訊息到公眾號裡 —

後台開發者中心會要求填寫 URL + Token, 然候 server 可以用別人寫好的 SDK

做驗證。驗證成功後會給 AppID + AppSecret 就可以利用這兩個 Key 使用高級 API 功能了 (獲取關注者列表)

註 :

PHP SDK 可參考 : http://www.cnblogs.com/xingmeng/p/3706676.html

“接收用戶消息” API

之後用戶留言在公眾號/服務號也都會 call 你提交出去的 URL, 也都會帶驗證的 $_GET 進來,

也就是每一次 POST 進來的值都要 check signature, 然候再處理你收到的訊息

註 :

用戶訊息不會在 GET 或 POST 裡, 必須要用 $GLOBALS["HTTP_RAW_POST_DATA"] 接,

傳進來是 xml 格式的字串, 再用 php 內建的 simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA) 解析

因為傳進來會包含 ![CDATA[ 所以加其他參數把它濾掉, 但是圖文訊息就要注意了

Logrotate

(最後更新: 2016-04-27)

介紹

設定 log 定期壓縮及輩份,通常是 /var/log 下的 log,主要是控制 log 的大小不要越來越大

如何設定

有兩個地方可以設定

  • /etc/logrotate.conf : 直接寫在裡面
  • /etc/logrotate.d/ : 新增你想要控制 log 的程式,比較建議寫在這裡,一個程序 (i.e. nginx) 就是一個檔案,方便管理

但不管寫在哪裡效果都是一樣的,因為 /etc/logrotate.d/ 下的檔案最後也是會被讀取到 /etc/logrotate.conf 裡執行

參數

  • daily | weekly | monthly : 進行 rotate 的間隔
  • rotate 7 : 保留 7 份 log, 也就是只會保留到 mysql.log.7.gz
  • create : 如果 log 被改名,建立一個新的繼續儲存
  • create 0664 root web-admin : 設定新建的 log 檔的權限與擁有者/群組
  • dateext : 被輪替的檔案加上日期作為檔名
  • compress : 壓縮輩份的 log,預設使用 gzip
  • minsize 50M : rotates only when the file has reached an appropriate size and the set time period has passed. e.g. minsize 50MB + daily If file reaches 50MB before daily time ticked over, it’ll keep growing until the next day.
  • maxsize : will rotate when the log reaches a set size or the appropriate time has passed. e.g. maxsize 50MB + daily. If file is 50MB and we’re not at the next day yet, the log will be rotated. If the file is only 20MB and we roll over to the next day then the file will be rotated.
  • size 10M : will rotate when the log > size. Regardless of whether hourly/daily/weekly/monthly is specified. So if you have size 100M - it means when your log file is > 100M the log will be rotated if logrotate is run when this condition is true.
  • missingok 表示如果找不到 log 檔也沒關係
  • delaycompress 表示延後壓縮直到下一次 rotate
  • notifempty 表示如果 log 檔是空的,就不 rotate
  • copytruncate 先複製 log 檔的內容後,在清空的作法,因為有些程式一定 log 在本來的檔名,例如 rails。另一種方法是 create。
  • If the size directive is used, logrotate will ignore the daily, weekly, monthly, and yearly directives.
  • rotate 完的 log 檔大約會是原檔大小的 1.2/10 e.g. 452MB -> 50MB, 1.5GB -> 170MB, 建議在記算 size 跟 rotate 次數的話抓, 1.5 比較保險

語法參考

你也可以一次指定兩個 log 位置

/var/log/mysql.log /var/log/mysql/*log {

Apache /etc/logrotate.d/apache2 :

/var/log/apache/*.log {
        daily
        missingok
        rotate 30
        compress
        delaycompress
        notifempty
        create 640 root adm
        sharedscripts
        postrotate
                if /etc/init.d/apache2 status > /dev/null ; then \
                    /etc/init.d/apache2 reload > /dev/null; \
                fi;
        endscript
        prerotate
                if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                        run-parts /etc/logrotate.d/httpd-prerotate; \
                fi; \
        endscript
}

Nginx (預設) /etc/logrotate.d/nginx :

/var/log/nginx/*.log {
    weekly
    missingok
    rotate 52
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    prerotate
        if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
            run-parts /etc/logrotate.d/httpd-prerotate; \
        fi \
    endscript
    postrotate
        invoke-rc.d nginx rotate >/dev/null 2>&1
    endscript
}

Rails production logrotate /etc/logrotate.d/rails :

/var/www/practice_rails/log/*.log {
  daily
  missingok
  rotate 7
  compress
  delaycompress
  notifempty
  copytruncate
}

何時生效

設定好之後,可以等明天或是執行強制 rotate

sudo logrotate -f /etc/logrotate.conf

如果設定的 logrotate 沒運作成功

使用 -v 啟動顯示模式,會顯示 logrotate 運作的過程

$ sudo logrotate -v /etc/logrotate.conf

reading config file /etc/logrotate.conf
including /etc/logrotate.d
reading config file apport
reading config file apt
reading config file aptitude
Ignoring test-program because of bad file mode.
reading config file dpkg
reading config file landscape-client

可以看到某一個被 Ignore 了, Ignoring test-program because of bad file mode.

這是因為 logrotate 要求你的 .conf 的 owner 是 root 且權限為 644, 如下:

-rw-r--r-- 1 root     ec2-user  146 Apr 17 09:35 test-program.conf

改完後再執行一次就可以正常運作了

  • -d : 開啟除錯模式。在除錯模式之下,紀錄檔將不會有任何的改變
  • -f : 不論設定檔是否符合需要 rotate 的條件,都會強迫執行 rotate 的動作

手動觸發生效

command

sudo /etc/cron.daily/logrotate

讓 logrotate 執行更頻繁

預設執行 logrotate 的時間是每天, 所以即使 log 超過了設定的 size 也不會被 rotate, 要等到下一天執行時才會觸發

每小時執行: on Ubuntu, you can easily switch to hourly rotation by moving the script /etc/cron.daily/logrotate to /etc/cron.hourly/logrotate

每 5 mins 執行: add above to your /etc/crontab file. To run it every 5 minutes.

*/5 * * * * /etc/cron.daily/logrotate

reference

Backend

安全性

  • SSL 憑證及加密傳送 (HTTPS)
  • 資料再以 private key 做 AES CBC 方式加密
  • 對某些重要的值使用 private key 以 sha1 產生 signature 參數做驗證
  • token & Access-Control-Allow-Origin (CSRF)
  • htmlspecialchars (XSS)
  • SQL injection

HTTPS

加密所有 request 及 response 資料, 包含 URL, 如果你使用 Http Proxy, 它只知道目標 server 的 domain, 不會知道 url path

意思是如果攔截到 https 封包只會看到 IP 跟 domain

設計 RESTful API 注意地方

避免使用 cookie, 因違反無狀態協議, 如果只用來記錄 client 資料不維護狀態, 那麼還是可以使用

session 則完全違反 REST 的無狀態性

解決方式是 client 登入後產生 token, 再從 url 帶 token 過來

驗證身份方法1. 永遠使用 ssl, 就不用每次讓用戶對每次請求簽名

驗證身份方法2. server 將 token 加/解密對應到 session ID, application server 再用 session ID 取得用戶資料判斷權限

增加網站效能

server

  • ELB (HA), auto-scaling
  • nginx 取代 apache

後端

  • background job
  • cache 靜態頁面
  • memcache / redis

前端

  • js/css minify & combine
  • 圖片用另一個 domain 避免 browser 一次只能發幾個 request 的限制, 也避免帶到不必要的 cookie
  • 一開始先載 lazy_image, 放真正圖片連結在 data-original= 最後再用 js 將 load 正確圖片

mysql

  • 使用 in 代替 or
  • slow query
  • Mysql set index
  • master - slave

Http

GET

GET 是我們最常使用也是最簡單的,它帶資料的方式就是把 query string 放在網址後面 :

example.com?f1=v1&f2=v2

GET 沒有 Body, 整個 Http 封包會像這樣 :

GET /api/test?f1=v1&f2=v2 HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: 0f99b8e9-0322-952e-a67c-76b498c51903

POST form

當要背景傳送資料時要使用 POST,也就是我們常在用的表單,它的實現原理是在 Header 加上 Content-Type: application/x-www-form-urlencoded

而 Body 就是 query string :

f1=v1&f2=v2

整個 Http 封包會像這樣 :

POST /api/test?f1=f1&f2=v2 HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: 3f036f1b-dfec-1269-41b7-bbf6781cbac4

f1=v1&f2=v2

POST 可同時支援 GET 及 POST 的參數

POST with file

如果要傳送檔案,就不是用原本的 Content-Type: application/x-www-form-urlencoded,要改用 Content-Type: multipart/form-data

雖然說 Content-Type 使用後者,但仍然一樣可以傳送 form data

你的 params 會被拆解成一塊一塊的,整個 Http 封包會像這樣 :

POST /api/test/ HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: b953aac1-b8d5-f1e4-36ac-2672b3a028fc

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="image"; filename=""
Content-Type:

data:application/octet-stream;base64,/9j/4AAQSkZJRgABAQEASABIAA....
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="f1"

v1
------WebKitFormBoundary7MA4YWxkTrZu0gW--

關於 App

  • 手機裡的 App 會有個 UUID,不同的 App UUID 也會不一樣,即使同 App
  • App 重新安裝 UUID 會是新的,或者 UUID 好像也有過期時間 (e.g. 180 天)
  • 當 app 下載完 UUID 就會像 e.g. APN 註冊,所以 Push Notification 才知道要推給誰
  • app 可以自已選擇要不要濾掉 notification,也就是其實發送成功但忽略它
  • Android 的 GCM 沒有分環境,都是用 Production; APN 有分環境有分 Sandbox 及 Production

Storage

  • 手機本身有兩個儲存的方式,一個是 sqlite,另一個是 local storage
  • sqlite 是一個檔案,所以讀寫要開檔跟關檔
  • local storage 是 key-value 型態,直接用就可以了

Notification Title

  • 如果 app 在前景 android 可以攔截, 並且指定要給的 title, 如果 app 在背景會由系統自行判斷, 如果 notification json format 有 title 的話會使用 title 的值當 notification title

app 與 server 溝通的幾種方式

  1. 原本的 silent push notification (APN/GCM)
  2. websocket (app 也能用 websocket)
  3. 使用主流的 library/sdk 來幫忙 e.g. google firebase / aws contigo
  4. mqtt, ampq, etc. messaging protocal
  5. AWS AppSync 可讓您輕鬆地建立協作式應用程式,以即時更新共享資料。您只要用簡易程式碼陳述式指定應用程式的資料,AWS AppSync 就會管理即時更新應用程式資料的所有必要作業。此外,AWS AppSync 還會在離線使用者重新連上網路時立刻更新相關資料。它只會擷取必要的資料,藉此降低訊息流量並提升應用程式效能和電池壽命。

Distributed System (Sub/Pub)

  • MQTT
    • 主要用在 IoT
    • MQTT is a lightweight protocol working only with a broker in the middle with no concept of queue (it can store messages when a client is offline using the “clean session” feature). It has another feature over AMQP like the “will” testment. It supports only pub/sub and have no metadata in the messages.
    • mqtt over websocket, 讓 app/website 使用 websocket 連接 server, 利用 mqtt 做 sub/pub 功能
  • AMQP
    • AMQP is more oriented to messaging than MQTT. It was created by JP Morgan for business transactions.
  • MQTT vs AMQP
    • The first big difference with MQTT is that AMQP 1.0 is a peer-to-peer protocol : you can use it between two peers, no need for a broker in the middle. Of course it’s used for communication with broker providing store-and-forward mechanism. You can use it for request/reply pattern and for pub/sub.
  • kafka
    • 主要用在 log 收集
    • 每個 topic 劃分多個 partition
    • 內部實現不用 cache, 都存在硬碟
    • data 會批量壓縮傳送
    • kafka 會將 data 儲存下來, 訂購的可以自已控制 pull
  • Redis pub/sub

Log 系統

  1. CloudWatch Log agent 丟到 cloudwatch 或是直接用 Kinesis agent 丟到 Kinesis Streams
    • 上面任一方法 -> Kinesis Firehose (log) -> s3
    • 上面任一方法 -> Kinesis Firehose (log) -> Kinesis Analytics (SQL query) -> Firehose (result) -> s3
  2. ELK stack : ElasticSearch(搜尋引擎) + Kibana(顯示圖表/結果) + Logstash (收集 log, 也可用 filebeat/FluentD)
  3. Telegraf / Collectd + InfluxDB + Grafana 易安裝
  4. Fluentd + Prometheus + Grafana

2,3,4 都要自已架, 成本也高

ref: * https://rickhw.github.io/2017/03/02/AWS/Study-Notes-CloudWatch/

DST 時間 (日光節約時間)

某些國家會調整當地時間以符合日光節約時間,當程式在處理不同國家時間時就要注意了,將時間轉成 dst 存下來, 可以使用別人寫好的第三方來轉,下次取時再將 utc 轉成 dst 撈出來。

如何避免 response header X-Content-Type-Options: nosniff

如果 MIME 類型與 URL 的 MIME 類型不匹配,則不允許讀取該檔案

我還不是很了解這個實際判斷的方式是什麼, 但我加上 cookie 後, 就可以下載 google drive 的檔案了

CORS

chrome 會先發一個 method: OPTIONS 的 request 測試是否通, 但不會幫你加上 body 及 header

Proxy vs Reverse Proxy

Proxy 後面的 server 有直接對外

Reverse Proxy 後面 server 沒有直接對外

編碼

輸出 emoji / unicode character / 表情符號

"\xF0\x9F\x98\x81"     // 用 Bytes (UTF-8 encoding) 輸出笑臉
"\uD83D\uDE01"         // 用 Unicode (UTF-16 encoding) 輸出笑臉
"\U0001F601"           // 用 Unicode (UTF-32 encoding) 輸出笑臉

utf-8 與 unicode 的不同

  • UTF-8 is an encoding
  • Unicode is a character set

以下是 string 用 unicode 存到硬碟到取出解碼的過程

1) 將文字轉成 unicode

A character set is a list of characters with unique numbers (these numbers are sometimes referred to as “code points”).

For example, in the Unicode character set, the number for A is 41.

2) 將 unicode 用 utf-8 編碼成 binary

An encoding on the other hand, is an algorithm that translates a list of numbers to binary so it can be stored on disk.

For example UTF-8 would translate the number sequence 1, 2, 3, 4 like this: 00000001 00000010 00000011 00000100

Our data is now translated into binary and can now be saved to disk.

3) 將 binary 用 utf-8 解碼成 unicode

Say an application reads the following from the disk: 1101000 1100101 1101100 1101100 1101111

The app knows this data represent a Unicode string encoded with UTF-8 and must show this as text to the user. First step, is to convert the binary data to numbers. The app uses the UTF-8 algorithm to decode the data. In this case, the decoder returns this: 104 101 108 108 111

4) 將 unicode 轉回成 string

Since the app knows this is a Unicode string, it can assume each number represents a character. We use the Unicode character set to translate each number to a corresponding character. The resulting string is “hello”.

Linux Email 寄信

sendmail

使用本機寄信的套件

安裝

sudo apt-get install sendmail

測試

sendmail xxx@example.com
Subject: Test from example.com
Hello~anyone!
.

Q&A

如果網站 hostname 有改過, 會發現寄信很慢, 甚至要一分鐘以上

/etc/hosts127.0.1.1 加上以下兩個 hostname :

127.0.1.1   ubuntu 106-185-47-26 106-185-47-26.localhost
如果 example.com 當作 gmail 的信箱, 主機 hostname 又設定成 example.com 的話

如果寄到

sendmail xxx@example.com
Subject: Test from example.com
Hello~anyone!
.

就會得到寄不出信的錯誤

/home/jxxx/dead.letter... Saved message in /home/jxxx/dead.letter

/var/log/mail.log 也會報錯

sm-mta[17354]: s6T7p65B017354: Milter (opendkim): error connecting to filter: Connection refused by localhost

會有這樣的訊息, 是因為主機認為是寄到 localhost 的, 所以就不會寄出

解決方法是把 hostname 改掉

echo "newHostname" > /etc/hostname
hostname -F /etc/hostname

/etc/hosts 記得也要設對 :

127.0.0.1       localhost
127.0.1.1       newHostname newHostname.localhost

然候就能順利寄信了

如果找不出問題

可以用 sendmail 指令寄信並觀查 /var/log/mail.log 出現什麼

使用 mailgun

mailgun是一個可以幫你寄送 email 的服務, 你或許不想用本機的寄,

可能考量到容易被當垃圾信或是不希望 server 在寄信時被 block 住,

那麼你可以考慮用 mailgun, 它的好處有 :

  • 建立在 HTTP 協定, 所有 API 都是 RESTful 的, 白話點就是用 curl 就能寄信了
  • 提供主流語言的 library, 使用 Package management 裝完就可以直接用了
  • 免費 10,000 封 email 幫你寄, 不需預綁信用卡
  • Custom domain

使用自己的 domain

點選 Add New Domain, 輸入 jex.tw, 接著以下步驟照著他頁面上的指示設定

登入你的 domain 代管網站 (ex: godaddy), 到 DNS ZONE 那設定

CName (Alias)

Host        Points To
---------------------
email       mailgun.org

MX (Mail Exchanger)

Priority    Host    Points To
-----------------------------
10          @       mxa.mailgun.org
10          @       mxb.mailgun.org

TXT (Text)

Host            TXT Value
-------------------------
@               v=spf1 include:mailgun.org ~all
k1._domainkey   k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcRvHyhPC9tltbqtKqUCNxJ39l5QAqQm7NWipFV40XG4KF2WVm0brd6U360VDng97vzlHBkEBS+chpa11L+grAhxFiGewFYRQ3rATl90eA1NKpGSBlm4yqig0P+fo++1j1EiA2Gn3KllTdPnVrWsRLaPN+***************cKQIDAQAB

接下來等 mailgun 那邊都生效就完成了, MX 會稍微比較久一點才會 active

Test

使用 CURL 寄信測試, 將以下指令中的這行 -F to=your_email@gmail.com 改成你 gamil 帳號

curl -s --user 'api:key-7******************************5' \
    https://api.mailgun.net/v3/jex.tw/messages \
    -F from='Contact <contact2@jex.tw>' \
    -F to=jxxxlin@gmail.com \
    -F subject='Hello' \
    -F text='Testing some Mailgun awesomness!'

如果收到以下回覆代表 mailgun 回覆成功, 到 gmail 信箱查看信是否成功收到

{
  "id": "<20150520110053.16306.14890@jex.tw>",
  "message": "Queued. Thank you."
}

可以到 https://mailgun.com/app/logs/<your domain> 下查看寄信的狀況

將 GoDaddy 購買的 domain 綁到自己的 Gmail

gmail 必須為企業版, 一個月一個帳號 5 美金

1) 先到 google apps for work 註冊

2) 為 GoDaddy 代管的網域建立 MX 記錄, 登入後設定 DNS

在 MX 紀錄那把預設的資料刪除, 加上以下設定

Priority    Host    Points To
10          @       ASPMX.L.GOOGLE.COM
20          @       ALT1.ASPMX.L.GOOGLE.COM
30          @       ALT2.ASPMX.L.GOOGLE.COM
40          @       ASPMX2.GOOGLEMAIL.COM
50          @       ASPMX3.GOOGLEMAIL.COM

如果有用 mailgun 或他服務寄信直接刪掉之前 MX, 新增 Gmail MX 就好了, mailgun 依然可以寄信

簡易方式避免被分到垃圾郵件

1) 使用 Gmail SMTP 寄信, 但一天最多寄 100 封 (Web UI 的話一天是 500 封)

2) 設定 From Reply-To, 並且確認 server 與 domain 是在同一個地方

From: Robot<robot@jex.tw>
Reply-To: contact@jex.tw

ref :

壓力測試工具

Siege

設定檔 /etc/siege

  • siegerc : 設定檔, 可以將 verbose 改為 true, 預設就是 -v
  • urls.txt : 將 url 或 post 資料寫在裡面, 下指令可以不用指定 url, 預設會讀取這裡

Example

siege -c200 -t10S www.google.com -v
  • -c, –concurrent=NUM CONCURRENT users, default is 10
  • -t, –time=NUMm TIMED testing where “m” is modifier S, M, or H (ex: –time=1H, one hour test.)
  • -v, –verbose VERBOSE, prints notification to screen.

結果 :

Transactions:                    652 hits
Availability:                 100.00 %
Elapsed time:                   3.09 secs
Data transferred:               4.74 MB
Response time:                  0.14 secs
Transaction rate:             211.00 trans/sec
Throughput:                     1.54 MB/sec
Concurrency:                   29.20
Successful transactions:         734
Failed transactions:               0
Longest transaction:            0.60
Shortest transaction:           0.00
  • Response time 超過 2 秒就代表網站效能很慢了
  • Concurrency 是每秒能同步處理的 request

可搭配 htop 觀看 cpu 動態

boom

golang 寫的類似 siege 功能的壓力測試工具

Install

go get github.com/rakyll/boom

jex@jex:/tmp$ boom -n 1000 -c 100 http://example.com
1000 / 1000 Boooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo! 100.00 %

Summary:
  Total:        15.4117 secs.
  Slowest:      4.6350 secs.
  Fastest:      0.0971 secs.
  Average:      1.4694 secs.
  Requests/sec: 64.8857

Status code distribution:
  [200] 1000 responses

Response time histogram:
  0.097 [1]     |
  0.551 [34]    |∎∎
  1.005 [24]    |∎
  1.458 [284]   |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  1.912 [649]   |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  2.366 [4]     |
  2.820 [1]     |
  3.274 [1]     |
  3.727 [0]     |
  4.181 [0]     |
  4.635 [2]     |

Latency distribution:
  10% in 1.3187 secs.
  25% in 1.4365 secs.
  50% in 1.5051 secs.
  75% in 1.5825 secs.
  90% in 1.6727 secs.
  95% in 1.7552 secs.
  99% in 1.9030 secs.

Node.js

Install node.js & npm (package manager) & nvm (version manager)

Ubuntu

sudo apt-get update
sudo apt-get install nodejs nodejs-legacy
sudo apt-get install npm
git clone git://github.com/creationix/nvm.git ~/.nvm
. ~/.nvm/nvm.sh

Add path to .bashrc

echo ". ~/.nvm/nvm.sh" >> ~/.bashrc

Amazon Linux

(?) sudo yum install gcc-c++ make
sudo yum install nodejs npm --enablerepo=epel

Websocket example

npm install websocket -gd

test.js :

var sys = require("sys"),
my_http = require("http");
my_http.createServer(function(request,response){
    sys.puts("I got kicked");
    response.writeHeader(200, {"Content-Type": "text/plain"});
    response.write("Hello World");
    response.end();
}).listen(9090);
sys.puts("Server Running on 9090");

Execute : node test.js Browser : http://192.168.56.1:9090/

  • -g : executable install globally
  • -d : node_modules 安裝到 /usr/local/lib/node_modules/ 目錄裡
  • 在 .bashrc 設定 export NODE_PATH=/usr/local/lib/node_modules
  • 以上是安裝在全域的 lib 需要的設定, 如果不加上 -gd 就會在目前目錄產生 node_modules

redis

install :

npm install redis -gd

SAVE OR BGSAVE 指令, 快照儲存的路徑 : /var/lib/redis/dump.rdb

Install express web framework :

npm install express -gd
npm install -g express-generator
npm install -g ejs

Use (example: test):

express test --ejs
cd test
npm install          (install dependencies)
npm start
  • ejs 是一種 html template engine, 語法不像 default 的 jade 跟原生的 html 語法差很多,
  • ejs 比較貼近原始的 html, 用法跟 php 的 template 很像

Install nodemon

檔案變動不用再重新啟動 node.js

安裝

npm install nodemon -g

啟動

cd test
nodemon ./bin/www

修改 `test/package.json` 將 `node ./bin/www` 改成 `nodemon ./bin/www`

ref

Packages

jshint : 檢查語法錯誤

npm install jshint -g

log.io

一個現成的套件提供介面看 log

建議安裝使用 root 安裝,比較不會遇到其他的麻煩 sudo npm install -g log.io --user="root"

Nginx

Install

sudo apt-get update
sudo apt-get install nginx

啟動

sudo service nginx start

到 browser 看是否安裝成功 ex: http://192.168.56.1

nginx www default directory : /usr/share/nginx/html/

如果啟動 nginx 不成功/沒反應

先判斷設定檔語法是否都正確

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

查看 /var/log/nginx/error.log 是否有噴錯

2017/08/07 08:51:59 [emerg] 2032#0: bind() to 0.0.0.0:80 failed (98: Address already in use)
2017/08/07 08:51:59 [emerg] 2032#0: bind() to [::]:80 failed (98: Address already in use)

重新安裝 nginx (Ubuntu)

sudo apt-get purge nginx nginx-common nginx-full
sudo apt-get install nginx

nginx.conf

/etc/nginx/nginx.conf :

user  www-data;
worker_processes  1;                                # CPU 數量, 可以避免 connection lock

events {
    worker_connections  1024;                       # concurrent connection
    use epoll;
}

403 Forbidden

建議設定 error_log 才能知道問題出在哪裡, 有可能是網站根目錄的權限不對

Rails config

/etc/nginx/sites-available/default :

http {
    passenger_root /home/jex/.rvm/gems/ruby-2.1.3/gems/passenger-4.0.50;
    passenger_ruby /home/jex/.rvm/gems/ruby-2.1.3/wrappers/ruby;

    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    error_log /var/www/pumpkin/log/nginx_error.log;
    access_log /var/www/pumpkin/log/nginx_access.log;
    server {
        listen       80;
        server_name  a.jex.tw;
        root /var/www/pumpkin/public;
        passenger_enabled on;
        passenger_set_cgi_param SECRET_KEY_BASE "cf2d4472039660a31a002b21cd3ded0cf7cc2c5a0d82f24dcdf5097b79c1900241f97eb85542f8e4a349f32fac37b618bc663b21f16de2706bb897885d6cc3f0";
        client_max_body_size       50M;     # 最大上傳 50MB

        error_page   500 502 503 504  /50x.html;

        location = /50x.html {
            root   html;
        }
    }

}

php (php5-fpm) config

第一次使用 nginx 執行 php 請先參考本文下方 php5-fpm

/etc/nginx/sites-available/default :

http {
    server {
        listen       80;
        root  /var/www/php;
        index index.php index.html index.htm;
        server_name  php.jex.tw;

        # 上傳大小
        client_max_body_size 50M;
        client_body_buffer_size 128k;

        # log
        access_log /var/log/nginx/jex-access.log;
        error_log /var/log/nginx/jex-error.log;

        # error page
        error_page 404 /404.html;
        error_page 500 502 503 504 /50x.html;

        location / {
            try_files $uri $uri/ /index.php;
        }

        location = /50x.html {
            root /usr/share/nginx/www;
        }

        # pass the PHP scripts to FastCGI server listening on the php-fpm socket
        location ~ \.php$ {
            # fastcgi_pass   127.0.0.1:9000;
            fastcgi_pass unix:/var/run/php5-fpm.sock;                           # 一定要指定, 要跟 php-fpm config 設定的一樣
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;

            # Server variable
            fastcgi_param DEVELOPER 'jex';
        }
    }

}

修改完重啟

sudo service php5-fpm restart
sudo service nginx restart

Multi sites 新增在 sites-enabled 下

有些方法安裝 nginx 完沒有 sites-enabled 資料夾, 必須自行新增資料夾再引入

/opt/nginx/conf/nginx.conf :

http {

    ...略...

    server {

        ...略...

    }
    include /opt/nginx/conf/sites-enabled/*;
}

/opt/nginx/conf/sites-enabled/php-nginx.conf :

server {
    ...略...
}

nginx 預設 user 是 nobody

不同 site 的網站根目錄的權限建議設定為 myUser:www-data,

讓 myUser 開發及 www-data 存取目錄或檔案時不會發生權限問題

如果本身己存在 sites-available 及 sites-enabled

建立新的 sites 就不用那麼麻煩了, 使用 symblic link

sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/www
sudo ln -s /etc/nginx/sites-available/www /etc/nginx/sites-enabled/www

並重新啟動 nginx

nginx 預設看似沒有像 apache 的 a2ensite 及 a2dissite 指令, 所以手動操作

Remove nginx

sudo apt-get remove nginx       # Removes all but config files.
sudo apt-get purge nginx        # Removes everything.
sudo apt-get autoremove         # After using any of the above commands, use this in order to remove dependencies used by nginx which are no longer required.
rm -rf /etc/nginx               # remove the conf files too.

其他設定

IPv6 + IPv4

listen [::]:80;         # 前面的是 IPv6

設定 SSL

到 Godaddy 買好 domain, SSL, domain 不要買 private 的

根據網路上找到很多資料都說 domain 不能 private, 但我仍然在 domain 是 private 的情況下完成 ssl 的設定 at 20150822, 所以以下操作可以等到輸入 CSR 後的 SSL 認證信收不到後再考慮是不是要拿掉 domain private

先確定 whois 可以查到 domain 的 email, 如果沒有查到代表 domain 被設定為 private 了, 可操作以下步驟解除

Godaddy 設定頁 點 DOMAINS -> 欄位 Associated Products 的 Domains By Proxy

(你也會看到 Registration Type 為 Private, 如果沒有保護的話會顯示 Public)

取消流程可參考此網頁, 流程大約是

在 Account 的 Domain 下點 Domains By Proxy 然候選擇 忘記密碼, 因為那個網站你還沒有帳密, 接著輸入網址後它就會寄信給你了, 收信取得 customer number, 登入帳號為 customer number 密碼為 godaddy 的密碼, 進入 dashboard 點 Cancel private registration

設定 dns

example.com 及 www.example.com 指到主機 IP

A (Host)
Host    Points To       TTL
---------------------------
@       139.162.11.41   1H
www     139.162.11.41   1H

Create the SSL Certificate

sudo mkdir /opt/nginx/ssl
cd /opt/nginx/ssl
openssl req -new -newkey rsa:2048 -nodes -keyout example.key -out example.csr             # 如果申請 example.com, 輸入 example.key, example.csr

接下來會問你幾個問題, 至少填第一個, 否則 Certificate 可能建立不起來. If you enter '.', the field will be left blank.

Country Name (2 letter code) [AU]:TW
State or Province Name (full name) [Some-State]:Taiwan
Locality Name (eg, city) []:Taipei
Organization Name (eg, company) [Internet Widgits Pty Ltd]:example
Organizational Unit Name (eg, section) []: (不用輸入)
Common Name (e.g. server FQDN or YOUR name) []:example.com
Email Address []:jxxxlin@gmail.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []: (不用輸入)
An optional company name []: (不用輸入)

Godaddy 設定頁 點 SSL CERTIFICATES -> manage -> CSR 貼上 .csr 的內容

-----BEGIN CERTIFICATE-----
MIID8zCCAtugAwIBAgIJAOT8G1V0fwvRMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYD
VQQGEwJUVzEPMA0GA1UECAwGVGFpd2FuMQ8wDQYDVQQHDAZUYWlwZWkxGzAZBgNV
BAoMEm1lZXR0aGV0cmFuc2xhdG9yczEfMB0GA1UEAwwWbWVldHRoZXRyYW5zbGF0
b3JzLmNvbTEgMB4GCSqGSIb3DQEJARYRanh4eGxpbkBnbWFpbC5jb20wHhcNMTUw
ODIxMTY1OTI2WhcNMTYwODIwMTY1OTI2WjCBjzELMAkGA1UEBhMCVFcxDzANBgNV
BAgMBlRhaXdhbjEPMA0GA1UEBwwGVGFpcGVpMRswGQYDVQQKDBJtZWV0dGhldHJh
bnNsYXRvcnMxHzAdBgNVBAMMFm1lZXR0aGV0cmFuc2xhdG9ycy5jb20xIDAeBgkq
hkiG9w0BCQEWEWp4eHhsaW5AZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAvu8s2aL1Tq4UfaCcMoO0jZKzmOeuYDSlCn4CrJjZmctFqFeS
+TVY7YObvbTI7XFLzaru55mOMpVI4QurYFCnHKJsbPsOWIN2PBkPYxv5KuHmZRPj
d5YGBzqgFaEgIoPftfYmNII9NYxgRCW/hGkx4j9BquZB/CaGMe0Im26Hj/BS3yyZ
oA+LaxiCz6FqK2E0gS0vVce/7ECvuR7DxQr5GNRQ8gmNrgvly0nvgPjyEzKndfMh
gv99MRQ5r6JjaozLD6KlNQ8npoNoeiIAaA3Q/uGuYpgKVxqpZIxmC5kMMeGfQHYh
XZ2tsjKALk+EOOErV/Zwa5y6BrTAUhIznQLZ8QIDAQABo1AwTjAdBgNVHQ4EFgQU
tETSHyBIKhOzMcJMfLBEcIjJOfwwHwYDVR0jBBgwFoAUtETSHyBIKhOzMcJMfLBE
cIjJOfwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAtNokFSCUtwsK
o5nmhB8pS20sILKWjRWg32HUrPU+uaBwDfAASq5ZRGjUoLQMDrxDuo0HWtR+vUCw
OLqCrp7iWSUs7UW4jK2OzNKDyEqUfEPnGd1ZcW309b0oix45aUPXwlp4UzvvBTU+
hZ2UdGsb+Tu/0uPGCjCqJudbZvtESqGSYd5e5bJWnPpz95pibvtCiSsD8ta+S3++
XscKN4Qpb8bjJ877y7NPm9aud9qJ0RQ41tUa4pmWE3uDVcRSpJzcMRK1oyMuh9YN
ftUJMSIawf49PCiSqAwGmQfs9Ou/ZtIwqjTnnWJ2lbHCxd2Zd8TaGeyz/TIB6vIH
OwFSjQxxhQ==
-----END CERTIFICATE-----

去 Email 收信 (當初申請這個 domain 的 email)

Download Certificate 下載會選擇 HTTP Server 的型態, 沒有 nignx 選 other,

下載完是一個 zip 檔, 解壓縮出來是 b38412d0dd76c4f3.crt (伺服器 SSL 憑證) 及 gd_bundle-g2-g1.crt (中繼憑證 (Intermediate Certificate))

nginx 只需要兩個參數,將這兩個檔案搬到 /opt/nginx/ssl 下, 並且合併成同一個檔案 :

cat b38412d0dd76c4f3.crt gd_bundle-g2-g1.crt > example_combined.crt
  • godaddy zip 也有可能有4個檔案, 網站憑證, root 憑證, chain 憑證及 bundle 憑證(root + chain)
  • (?) 如果是 apache 就要放 網站憑證, chain 憑證跟 bundle 憑證

設定 nginx.conf, 填上 SSL 設定

強制將 non-www 及 http 導到 https://www

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
server {

    listen 443 ssl;
    ssl_certificate /opt/nginx/ssl/example_combined.crt;
    ssl_certificate_key /opt/nginx/ssl/example.key;

    listen       80;
    server_name  example.com;

    # non-www redirect to www
    if ($host = $server_name) {
        return 301 https://www.$server_name$request_uri;
    }

    # 將 http 導到 https
    if ($scheme = http) {
        return 301 https://www.$server_name$request_uri;
    }

    ...(略)...

}

完成!

https://www.example.com

[問題] 如果 Https 不是綠色的而是灰色的

可能出現此訊息

這個網頁含有其他不安全的資源

看一下 developer tool 的 console, 顯示什麼提示訊息,

例如網頁上如果用 <img> 來源是別人的圖片, 如果不是 https 就有可能出現此訊息

判斷您的 SSL 是否安全

disable SSL 3 to mitigate 問題

POODLE 是 protocol 層級的弱點, 很難修復, 它可以透過兩個方面去防止這個攻擊

第一個就是 user 將瀏覽器的 SSL3 關掉, 第二個就是網站工程師要做的, 在 Server 端將 SSL 3 關掉

在 nginx.conf 裡指定 ssl protocols

http {

    ...(略)...

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    ...(略)...

    server {
        ...(略)...
    }
}

可以在 http 層指定 或每個 server 層指定

也因為修正這個漏洞原本被 QUALYS SSL LABS 評為 C 而升到 B 了

讓 nginx 執行 php (php5-fpm)

sudo apt-get install php5 php5-cli php5-fpm php5-mcrypt php5-mysql

啟動 php5-fpm

sudo service php5-fpm

將 fpm 與 nginx 連接起來

原理

當 request 進來, nginx 接到後會往 php-fpm 裡送, 透過它的 fastcgi 去執行 php

修改 nginx 設定檔

為了讓 nginx 可以執行 php 的 code, 到 /etc/nginx/sites-available/www

找到這行 # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000

除了這行 fastcgi_pass 127.0.0.1:9000; 將其他被註解的打開

因為 fpm 預設會使用 unix socket (php-fpm 設定裡預設用 listen = /run/php/php7.0-fpm.sock), 所以在本機跑會比較快, 如果要交由一台效能較好的

server 去處理 php 可以在 /etc/php5/fpm/pool.d/www.conf 設定改用 tcp socket (listen = 127.0.0.1:9000) 而 fpm 就會用 tcp socket 來運作了

如果要改 php 的執行 user 及 group 也在 /etc/php5/fpm/pool.d/www.conf 裡面改

修改 root path : 網站根目錄路徑

index.php 也設為預設的首頁

index index.html index.htm index.php;

在網站根目錄新增 index.php 看結果是否成功

fpm 設定檔 : /etc/php5/fpm/pool.d/www.conf, 要改 http server 執行的 user, group 在這改

If php5-fpm not working

1) 檢查 /etc/php5/fpm/pool.d/www.conf

listen = /var/run/php5-fpm.sock
user = www-data
group = www-data

2) 確認 /var/run/php5-fpm.sock 權限

srw-rw---- 1 www-data www-data 0 May  6 17:29 /var/run/php5-fpm.sock

3) 確認 nginx 的執行權限

user  www-data;

4) 檔案的執行權限

-rw-rw-r-- 1 www-data www-data   81 May  5 08:04 index.php

Config 效能調校

  • worker_processes : 與 CPU 核心數一樣
  • worker_connections : 每個 process 最多能承載的連線數
  • use epoll : 高併發下效能較好
  • client_header_buffer_size 4k;
  • keepalive_timeout 60;
  • open_file_cache max=65535 inactive=60s;

ref : http://blog.didibird.com/2016/05/24/the-way-of-turning-the-best-performance-for-nginx/

安裝 ngx_pagespeed (重新安裝整個 nginx)

無法直接加入 ngx_pagespeed module, 必須把原本的 nginx 刪掉再重編

照著安裝 : https://developers.google.com/speed/pagespeed/module/build_ngx_pagespeed_from_source?hl=zh-TW

Verify :

/usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.6.2
built by gcc 4.8.2 (Ubuntu 4.8.2-19ubuntu1)
configure arguments: --add-module=/home/jex/ngx_pagespeed-release-1.9.32.1-beta

Command

git clone https://github.com/Fleshgrinder/nginx-sysvinit-script.git
sudo cp nginx-sysvinit-script/nginx /etc/init.d/nginx
sudo chmod 0755 /etc/init.d/nginx
sudo chown root:root /etc/init.d/nginx
sudo service nginx start

Q&A

reponse 被截斷

回 json 但 response 只收到一半, 直接被截斷

如果以 FastCGI backend (such as PHP-FPM) 為底層, 會 buffer response 在記憶體, 預設 nginx buffer 是 32kb, 如果超過這個大小的話就會被截斷

設定不限制大小

fastcgi_buffering off;

[補充] 預設為

fastcgi_buffers 8 4k|8k;        # 8 等分, 後面是取決於平台, Ubuntu 就是 4k

ref : https://gist.github.com/magnetikonline/11312172

PHP Multi-Process

Example code :

echo "Start \n";

$pid = pcntl_fork();
echo "XD \n";
if ($pid)
{
    //$rtn = pcntl_waitpid($pid, $status);
    echo "father \n";
    $sum = 0;
    for ($i = 0; $i <= 1000000; $i++)
    {
        $sum += $i;
    }
}
else
{
    echo "child \n";
}

echo "End \n";

result :

Start
XD
father
XD
child
End
End

註 :

  • 用 pid 判斷是主程序還是子程序
  • 從結果可以看出 fork 出來的子程序會從 pcntl_fork() 下一行開始執行
  • fork 後不會等到主程序做完才做子程序, 是分時分段執行的, 也就是多程序的概念

如果要等子程序做完主程序再做的話要在主程序裡加上

$rtn = pcntl_waitpid($pid, $status);

結果為 :

Start
XD
XD
child
End
father
End

C/c++ 相關

Basic

  • int = 4 bytes
  • double = 8 bytes
  • char = 1 byte
  • 1 char 存的是 ASCII 數字

一般說來,我們編譯安裝一個由GNU Autoconf 配置的程序是採用如下的步驟:

./configure && make && make install

這個configure 腳本文件是用來“猜”出一系列系統相關的變量,這些變量是在後面的編譯過程要用到的。它將檢查系統變量值是否滿足編譯要求,然後使用這些變量在程序包內每個文件夾下生 成Makefile。此外,configure 腳本還會生成其它文件: 每個文件夾/子文件夾下的一個或多個Makefile(s) 一個名叫config.status 的腳本 一個文本文件config.log Configure 腳本文件成功運行之後, 你會輸入make 來編譯程序,得到你需要的可執行文件。如果make 成功的完成,你可以使用make install 來安裝這個程序。

ref : http://www.ibm.com/developerworks/cn/linux/l-cn-checkinstall/

c include path

在 GCC 編譯下 C header 的 #include <stdio.h> 預設會到以下4個路徑去找(非絕對, 不同系統有不同的路徑)

  • /usr/local/include
  • libdir/gcc/target/version/include
  • /usr/target/include
  • /usr/include
  • /usr/local/lib

也可以加上 -I dir 選項指定搜尋的路徑

gcc -I /usr/local/include/qq

.c .h Makefile

  • 函數定義放在 .c, 不要放在 .h, .c 是定義變數及實作函數
  • .h 只做宣告(變數、函數宣告)
  • .c 使用 #include 將 .h 引入
  • 一個 .c 最好對應一個 .h
  • 對編譯器來看 .c 及 .h 是一樣的, 只是分開寫會可以培養較好的 coding 習慣