RESTFul API 的 mock server 解決方案

會寫這一篇主要是最近在公司上有遇到我們需要快速迭代與試錯,但在前後端是不一樣的團隊,但在快速迭代的情況下,為了避免不要互相影嚮進度,一個常見的作法就是架設json mock server,也就是設定對應的api 只回應對應的假資料…

這類型的工具也很多,只是最近剛好摸到一個工具覺得是我目前碰過的類似工具之中,最容易上手的,而且可以有GUI可以使用…

Mockoon

這個工具與其它類似工具的主要差異是,它有GUI界面,可以直接在界面上設定要開哪些endpoint與對應的response以後,就可以直接在local 端跑起mock server了。

而且GUI 界面類似於 Postman,所以上手難度很低,稍微摸一下界面就知道它可以做哪些工具了…

Mockoon routes view
reference: https://mockoon.com/docs/latest/gui-cheat-sheet/

其它更多好用的功能像是

  • Proxy configurations

這個是當api request 打進時,mockoon上如果有沒設定的endpoint時,可以指定它轉發到特定的機器。

  • Support openapi/swagger config import

這個顧名思義是可以直接把我們寫的api 文件檔直接丟到mockoon上,這樣對應的endpoints 就被設定好了。

  • Rule based responses

這我覺得也算是蠻客製化的功能,我們可以針對一個endpoint設定多個responses,然後透過一些客製化的規則(rules)來設定mockoon 要回哪個response。

  • Support CLI based runtime

除了gui 界面以外,官方也有支援cli (用npm包的),所以我們可以把local端的mockoon設定檔丟到cloud上的機器,然後就可以在機器上用cli跑起一個對應的mock server。

像是下面一個簡單的 restart.sh 就可以讓重啟linux 上的mockcoon 並且讀取對應的設定檔。

!/bin/bash
 pkill -f "mockoon-cli"
 mockoon-cli start --data mockoon_api_config.json -p 9000 > /dev/null 2>&1 &

如何寫好Logs

寫好Log對我而言一直是一項重要的技能, 因為它可以讓問題發生時, 讓開發者用最短的時間找到並解決問題… 剛好在Hacker News上看到這篇 Logging in Python like a PRO , 順便紀錄一下這位作者的一些觀點…

好的Log所需具備的特性

* Descriptive
* Contextual
* Reactive

They’re descriptive in the sense that they give you a piece of information, they’re contextual because they give you an overview of the state of things at the moment, and finally, they’re reactive because they allow you to take action only after something happened (even though your logs are sent/consumed real-time, there’s not really something you can do to change what just happened).

https://guicommits.com/how-to-log-in-python-like-a-pro/

簡單來說如果一段log可以具備上述三個特性, 作者就認為可以讓log的讀者能夠更輕鬆的定位問題的發生點, 以及相對應的事件背景 !

Log 的時機點

As a rule of thumb consider logging:

* At the start of relevant operations or flows (e.g. Connection to third-party, etc);

* At any relevant progress (e.g. Authentication successful, got a valid response code, etc);

* At the conclusion of an operation (e.g. EITHER succeeded or failed);

https://guicommits.com/how-to-log-in-python-like-a-pro/

至於說何時要寫Log呢? 作者這邊指出事件的起始點, 相關的處理進度發生時, 以及事件處理完成時都是適合Log的時間點 !!!

Python 下的Logging的一些範例設定

Python上的一些Log設定我就不太熟了, 先紀錄一下之後可以看看….

總結

這篇文章算是簡單的提要了寫Log時的一些大方向, 這樣之後要寫也蠻適合當參考…

Reference:

透過Dehydrated來設定Let’s encrypt

之前是使用Certbot來更新我這個網誌的Let’s encrypt certificate,運作上也還算正常,但自動更新cert這邊常常更新不了(這部分感覺應該是我設定上的問題)…

後來在公司上, DK有提到了dehydrated這個輕量化的Let’s enrypt 設定工具,實際上去快速看了一下它的repoistory以後,真的覺得對於一般的Linux 環境使用上蠻友善的(因為它所需要的工具通常都是Linux 系統預設的工具)。

快速掃完以後,直接進入正題關於設定上的相關指令… (這邊直接參考 DK 的 wiki上的設定)

安裝Dehydrated

# using a temporary directory to download the dehydrated
cd ~/tmp
wget "https://raw.githubusercontent.com/dehydrated-io/dehydrated/master/dehydrated"
chmod +x dehydrated
sudo mv dehydrated /usr/local/bin/

Dehydrated環境設定

# create directories that dehydrated will need
sudo mkdir -p /etc/dehydrated /var/www/dehydrated

#change to the dehydrated dir
cd /etc/dehydrated

# setup the config
echo 'OCSP_MUST_STAPLE=yes' | sudo tee -a config
echo 'KEYSIZE=2048' | sudo tee -a config
echo 'WELLKNOWN=/var/www/dehydrated' | sudo tee -a config

# setup the domain file
echo 'blog.gechen.org' | sudo tee -a domains.txt
 

設定Nginx

這邊的範例主要是透過Nginx來當做之後dehydrated 執行時,用來回應Let’s encrypt 的challenge…

下面就直接貼Nginx 的對應domain的範例設定檔

server {
     listen 80;
     server_name blog.gechen.org;
     rewrite ^(.*) https://$host$1 permanent;
 }

server {
     listen 443 ssl;
     server_name blog.gechen.org;
     
     index index.php;
     ssl_certificate /etc/dehydrated/certs/blog.gechen.org/fullchain.pem;
     ssl_certificate_key /etc/dehydrated/certs/blog.gechen.org/privkey.pem;


     # ... skip some configs

   
     # this part is for dehydrated
     location /.well-known/acme-challenge/ {
        alias /var/www/dehydrated;
     }
 }

執行Dehydrated 來更新Let’s encrypt certs

如果是第一次使用 dehydrated 則要額外執行下面指令

sudo dehydrated --register --accept-terms

透過下面的指令來做Let’s encrypt certificate的申請/更新

# the following command will apply/renew certificates for the domains in file: /etc/dehydrated/domains.txt
sudo dehydrated -c

# or we can also directly use command below to apply for specific domain certificate
sudo dehydrated -c -d blog.gechen.org

設定自動更新certificate的cron jobs

這邊直接引用DK 大大的wiki範例來設定 weekly 的憑證更新…

echo -e '#!/bin/bash\nexport PATH=/usr/sbin:/usr/bin:/bin:"${PATH}"\nsleep $(expr $(printf "%d" "0x$(hostname | md5sum | cut -c 1-8)") % 86400); dehydrated -c && ( service nginx reload )' | sudo tee /etc/cron.weekly/dehydrated; sudo chmod 755 /etc/cron.weekly/dehydrated 

References:

Search Engine list

剛好在DK大大那邊看到的一篇搜尋引擎的整理文章, 我猜來源應該是Hacker News上有人發布的XDDD

文章的內容中提供了一大串的搜尋引擎相關的服務…. (沒想到有這麼多搜尋引擎阿阿阿!!!)
先紀錄一下, 找一天再來細看裡面收錄的搜尋引擎XDDD

reference:

過濾Google Search的結果

之前就看到的 open source solutions (uBlacklist), 但都沒特別去使用它,直到今天看HackerNews時又看到它,才想說來試用一下看看。

它主要的功能是透過browser extension 的方式來過濾掉使用者不想看到的網站,所以使用者需要安裝他們家的extendions。

下載位址在這:
Chrome Web Store / Firefox Add-ons / App Store

設定方式也很簡單,可以透過wildcard * 的方式,或是reqgular expression 的方式來去過濾掉不想看到的網址。(相關範例可以參考這裡)

另外還有個蠻方便的功能就是,在裝了extensions以後,在之後Google Search的結果中就會有個小按鍵可以讓使用者直接block當下他看到的某個搜尋結果。

Reference

AWS Lambda的”冷啟動”時間

在Hacker News 上看到的一些相關比較,作者比較了現有AWS Lambda 的程式語言,並記錄每種語言在不同情境下的cold start的時間。

測試架構大概如下:

clients -> HTTP API -> Lambda function -> Dynamo DB

直接跳到測試結果

Cold start:

* All languages(except Java and .Net) have a pretty small cold start.
* Java even cannot start with 128Mb. It needs more memory. But GraalVM can help in this case.
* Rust beats all runtimes for all setups for cold start, the only exception is 128 MB where Python is the best.

AWS Lambda battle 2021: performance comparison for all languages (cold and warm start) | by Aleksandr Filichkin | Sep, 2021 | Medium

Warm start:

* Golang and Rust are the winners. They have the same brilliant performance.
* .Net has almost the same performance as Golang and Rust, but only after 1k iterations(after JIT).
* GraalVM has a stable great performance almost the same as .Net and a bit worse than Rust and Golang. But it doesn’t perform well for the smallest setup.
* Java is the next after GraalVM.The same as .Net, Java needs some time(1–3k iterations) for JIT(C1). Unfortunately for this particular use case, I was not able to achieve the expected great performance after JIT C2 compilation. Maybe AWS just disabled it.
* Python has stable good performance but works too slow for the 128 MB.
* Ruby has almost the same performance as Python, but we see some duration growing after 20 min invocations(after 15k iteration).
* NodeJs is the slowest runtime, after some time it becomes better(JIT?) but still is not good enough. In addition, we see the NodeJS has the worst maximum duration.

AWS Lambda battle 2021: performance comparison for all languages (cold and warm start) | by Aleksandr Filichkin | Sep, 2021 | Medium

Go與Rust是其中最穩定的且效能最好的,在幾乎所有的情境下,都是前2名。(唯一的例外是,Python在128MB RAM 的環境下是最快的)。

對我而言比較意外的是Node.js,沒想到會是最慢的!?

Reference:

AWS Lambda battle 2021: performance comparison for all languages (cold and warm start) | by Aleksandr Filichkin | Sep, 2021 | Medium

Socks5 proxy server的設定

今天跟DK開會時,他利用些片段時間分享了他的工作環境設定,主要是關於他怎麼透過設定他的環境來同時可以連接dev, stage, production的環境。
會有這個分享主要也是因為我們在切換環境時,相對較笨拙點,都是透過連接不同的vpn server來切換到不同的環境下,但同時也只能在同時間點,只能連接到某個環境。

那就來進入主題了,他分享的方式,主要是透過在本機端建立socks5 proxy server, 將對於特定目標位址的流量,proxy到遠端預先設定好的jump server,再到對應的最終目標位址。

Step 1. 建立遠端的jump server

只要遠端的server有開啟ssh server的,就已經可以當成jump server了,而我們的stag jump server 就是這樣設定的。

Step 2. 設定 ~/.ssh/config (optional)

為了更容易的連接jump server,而不用每次都打一大串的ssh 指令,我們可以把連接jump server的相關設定寫到 ~/.ssh/config下,範例如下:

Host staging_jumper
   HostName our-jump-server.com
   IdentityFile ~/.ssh/jump-server.key
   User my-username

Step 3. 在本機端啟動socks5 server

由於剛剛在上一步有設定好連接jump server時,需要的ssh user 與 key,所以這邊可以很簡單的用下面的ssh指令啟動socks5 server

ssh -D 1081 -C -N -f staging_jumper

-D 1080 代表要將連往本機端 port 1081的流量轉送到staging_jumper上。
-f 代表要在背景執行
-C 代表要壓縮資料
-N 代表不執行任何遠端指令,通常做port forwarding 都會代入這flag

如果要關閉在背景的socks5 server,則可以用ps -aux 找出剛啟用的socks5 server pid,接著再使用kill指令去停掉正在運行的sock5 server process。

Step 4. 檢查是否有socks5 server是否有運作正常

curl --socks5 localhost:1081 http://your-staging-service.com

可以簡單透過curl 指令,來發起一個http的request到我們剛啟用的socks5 server port上。 如果成功的話,理論上會看到我們預期的html 網頁了。

Step 5. 設定SwitchyOmega

這是一個browser extension,主要是可以讓我們連接不同的網頁時,可以使用預先設定好的條件或方式來連接。
舉例來說,我們可以設定當接http://192.168.10.2:8080這個位址時,就自動透過我們預先設定好的socks5 server 與jump server 連接,此外,它還可以透過wildcard的方式來導流特定網段或特定domain 的網頁。

如以下範例

當我們啟用本機端的socks5 server以後,就可以到下面的SwitchyOmega頁面去設定代理伺服器。

之後,只需要再設定auto switch來決定哪些目標網頁要使用哪些代理伺服器就完成了

reference:

實作 API Rate limiter

最近剛好公司某個專案需要將資料傳輸到遠端的某個服務上,而那個服務可能會有頻寬上的限制,所以我們的資料傳送端必須加入Rate limit這功能,以免瞬間的資料流造成資料被丟棄的問題;也趁這個機會多整理一點 Rate limit相關的知識…

rate limiting vs throttling

這兩個名詞常分別或一起出現在討論rate limiting 的相關文章中,而我也常搞混這兩個詞的意義,所以稍微再整理一下它們所代表的意思:

Rate limiting 指的是,限制requests在某個時間區間內,其可允許執行requests的數量。

Throttling 指的是,控制requests在特定的時間區間內出現時,允許可執行requests的 一種流程。

一個簡單的例子是,當我們限制某個API 每秒只能執行 100次,這是一個Rate limiting的流程;而當我們再把時間看得更細時,平均我們每100 ms 可以服務10個requests。
在這 100 reqs/s的情境下,控制requests 可以被服務的頻率的一個程序就是throttling。
* 這邊我們可以控制當requests 超過 10 reqs/100ms,就被丟棄 或是可以允許requests 最多到burst 20 reqs/100ms)
* 另一種Throttling的情境是,在某一秒的requests數量為105,那超過的這5個requests 我們可以允許它們被服務(soft throttling)或直接丟棄那5個requests(hard throttling)

簡而言之,Rate limiting比較偏向資源服務在大方向的一個規範,而Throttling責聚焦在實作上一些情境下的處理流程。

Rate limiting 相關演算法

Token bucket

Token bucket 主要的概念是,想像我們有一個水桶,裡面最多可以放到n 個token;每使用者需要使用某個資源時,他就必須到水桶中取走一個token,而如果水桶中沒有任何的token時,則使用者無法使用他想要用的資源。

而水桶中的token 會依照 1/n second的速率補充到水桶中,當水桶補滿n個token時,就不再補充任何的token了。

特色:

  • 如果水桶中的token是滿的,其會允許使用者瞬間可以同時索取n個token,也就是允許requests 最多同時burst到n個。
  • 可能會導致某個時間區間下,各個時間點request量不平均。
  • Golang 有內建的lib 原生支援這個演算法。
  • 主要的應用在於,如果我們必須限制我們的requests在固定區間下是有限的,但區間下的每個時間點是允許突然暴增的requests。

Leaky bucket

Leaky bucket 是常用來與Token bucket比較的一個演算法;其概念上是,我們會有一個水桶,並且我們有定義好這個水桶可以往下流出的速率,當我們的requests的量大於可允許流出的速率時,則會被queue起來;而同時間我們也會定義水桶中最多可允許queue起來的量,如果超過時,則這些新的requests 則會被丟棄。

https://en.wikipedia.org/wiki/Leaky_bucket

特色:

  • 概念上對於burst requests的限制較嚴,主要是用來維持每個時間點下的requests流量是固定的。
  • Golang 中可以參考uber-go/ratelimit 這套 open source solution。
  • 比較適合像是,網路頻寬或流量控制的相關應用。

相關文章:

Fixed window counter

這個演算法的概念是,我們定義每個時間區間下可以允許的requests 量是n,而這個區間間是固定的。(例如 [12:00:01-12:00:02), [12:00:02, 12:00:03) )
每個區間會有個counter,每當有新的request時,我們就會有個計算目前這個區間下已count的量,如果加入這個request會導致count > n,則我們會丟棄這個新的request。

特色:

  • 實作上相對簡單,且對burst的限制較不嚴。
  • 如果requests 量剛好發生的時間在,前一個區間的下半段與下一個區間的上半段時,則可能發生這段跨區間的requests量是我們原本定義的n的2倍。
    (e.g. 假設我們每個區間的允許的量是n ,而有n個requests發生在[12:01:30, 12:02:00),另外n個requests發生在[12:02:00, 12:02:30),則如果我們單看區間[12:01:30, 12:02:30)時,會發現requests量可能暴增到2n。)

Sliding window logs

這個演算法類似於Fixed window counter,會針對每一個request記錄其發生的時間,所以當一個新的request出現時,演算法會去比較目前時間點回推到過去的某個時間點下,這段時間區間中的request數量,再來衛量這個新的request是否允許,如果不允許的話,則這個新的request則會被丟棄。

隨著時間的流動,已經過期的request 記錄也會被清除…

特色:

  • 解決了Fixed window counter演算法可能會遇到的request burst問題。
  • 實作上,可能計算會相對較秏資源;因為每次要確認一個新的request是否允許時,需要從目前時間點往回計算,而且過期的記錄要刪除也會是額外要做的事。

Sliding window counter

這個演算法是整合了Fixed window counter + Sliding window log的演算法的作法;概念上也是使用Fixed window counter的概念,只是每個時間區間又再切成多個子區間,所以每次在比對某個request是否可以服務時,就是比對目前時間點下的子區間再加上過去發生過的多個子區間之count 總合,如果低於某個值rate limit最初設定的值的話,那就代表這個新的request 是可以被服務的,反之則丟棄這個request。

舉例來說,我們定義某個API 的 rate limit 為每秒n個,則在實作上,我們可以多切10個子區間, 所以每個子區間的間隔會是100ms,以12:00:01這個區間為例,實際上記錄的子區間有12:00:01.1, 12:00:01.212:00:01.9 等等。
所以當一個新的request發生時,演算法會去計算目前這個子區間所有的count數量以及過去9個子區間count 數量的總合,再來比對加入這個新的request是否還是小於 n,如果是的話則允許服務這個api request。

特色:

  • 整合了Fixed window counter 與Sliding window log的優點,且實作上也不難實現。

在分散式系統上的一些實務上作法

使用Central Storage – Redis/memcached

這是一個蠻常見的作法,透過一個高效的儲存db來存放一些狀態資料,每當一個新的request發生時,就先去儲存db獲取先前儲存的狀態(token/count… etc)來判定目前的request是否可以被服務。

特色:

  • 效能瓶頸會是在redis/memcached本身,不過對大多數的應用來說應該是夠用了。
  • 網路上可以找到蠻多資源的,相關的教學文或已經寫好的open source library。
  • 若單存使用Redis/Memcached所提供的一般API時,可能在大量request發生時要小心處理race condition的問題。(或許要額外引入distributed lock之類的機制)
  • 如果使用Redis + Lua時,則可以實現atomic operation,解決race condition的問題。

相關文章:

使用client based rate limiting

概念上是rate limit 的管理是放在client service 那邊管理的,而所有的client service 會透過一個central storage 來獲取某個API/Resource的total rate limiting與client rate limiting 。

舉例來說,我們可以在etcd上設定某個API的rate limiting為n,並且也記錄了目前註冊過的client service數量m,所以每個client service 相對於這個API的rate limit就會是n/m, 這樣每次client service在服務request時就只需要直接參考目前本機上的rate limit (n/m)就可以了。
而在這個例子下,我們還可以透過ectd的watch 機制來讓所有的client service即時的獲取更新後的nm值。

特色:

  • 效能可以到非常好,因為rate limit是實作在client 端,所以特別適合超大流量的應用。
  • 有個先決條件為,流量必須很平均的分散在所有的client端,這樣才會更有效的使用到所有分配到rate。
  • 作為取捨,對於rate limit的控制會相對沒那麼精準,畢境如果有client端的service 掛掉了,會有段時間差,之後所有的client才會獲取得最新的資訊。

相關文章:

自行設計的rate limit cluster

mailgun這間公司有open source了一套Go的distributed rate limit service – guberator,其概念上是把micro services的概念應用到rate limit service上,每個client端的機器,都會另外在佈署這個gubernator peer,而gubernator之間是靠grpc來溝通的。

每個gubernator都會管理不同resource的rate limit,所以每當某個client service要服務某個request時,它會需要與本機端的gubernator詢問request 相關的limit,而本機端的gubernator 則會知道相對應的資訊是存在本機或是其它的gubernator上。

由於資訊還是由某個gubernator管理,所以理論上也會受限於單機可以處理的上限,但在這邊gubernator有透過一個自行實作的batch機制來提高機器處理的上限值。

這個機制也蠻容易理解的,主要就是再處理rate limit request之前,先把收到的requests 打包起來再送出去成一個單一的batch request。從文章上有提到,batch機制的預設是每當收到對於某個resource的第一個request時,會再等500 micro seconds來收集更多對同一個resource的請求,當時間到了以後,再處理這一包batch過的rate limit request;
假設在這段時間內,收到3000 個對於某個resource的rate limit requests,則只需要某個gubernator送出一個rate limit request,其request quota量為3000,接下來就只需要看收到request的gubernator它那邊會允許多少quote是可以被執行的。

相關文章:

Reference:

無所不在的SQL injection

一樣是在Hacker Daily News看到的一篇文章,作者說明了他在google上找到了30個關於php + email 註冊 的相關教學文章,發現其中的16筆是有SQL injection 的風險。

趁這個機會再來複習一下SQL Injection, 畢境這是非常常見的攻擊之 一;主要的攻擊手段是將一段有害的字串,透過網頁輸入的方式傳送至伺服器上,而伺服器的程式在沒有做任何檢查的情況下,就帶入網頁送上來的那些參數並執行了特定的SQL而造成問題。

從oswap上看到的一個典型的範例是,讓使用者在網頁端填入firstname與 lastname,然後伺服器這邊就透過使用者給的資訊來去資料庫找對應的使用者資訊,若此時使用者給的是像這樣的資訊

Firstname: evil'ex and Lastname: Newman

且執行的SQL可能會是長這樣, 其造成的問題就是會讓資料庫試著去執行evil 指令而失敗

select id, firstname, lastname from authors where firstname = 'evil'ex' and lastname ='newman'

事實上,還有更多網路上可以找到的攻擊範例,而解決方式就是在伺服器這邊需要針對使用者傳上來的資訊做更多的驗證,之後才可以使用。
另外,一些常見的web framework通常都會將這些驗證自動套用到程式中,所以如果我們有使用web framework的話,也可以看一下所使用的framework是否有對應的處理方式。

reference