imuslab
托比的實驗記錄部落格
香港人在新竹租房奇遇記(?)
最近因為在新竹科學園區找到了算是人生第一份正規的科技業工作,所以就準備打算直接搬家到新竹東區來方便上下班。然而想不到新竹的租房市場真的比想像中和資料收集中的還要坑人(特別是新手),所以就來寫一篇部落格來記錄一下。 事前資料收集 第一次做任何事情之前,最重要的就是做資料收集了。首先,在新竹科學園區附近找房子,有幾個重點: 591 上面的都是釣魚的Facebook 租屋群,騙子超多,下面再詳細解釋本地的不動產(門市),通常都只會登出超難租出或是貴價的,學長說那種風險很高不建議考慮掃街(建議的方法,但是很浪費時間和體力) 591 新竹租房問題 可能對於第一次來台灣的香港人,最早接觸到的租(買)房平台就是 591 了。可是在新竹的租房情況下,591 一般會出現釣魚問題。 簡單來說是 591 上面都只有仲介,放出來的圖片是真的,物件也應該是真的,但是你看到的時候通常都已經租出了。當你真的約房仲出來看的時候(就是你人已經去到新竹的時候),他才跟你說房子已經沒了,要不要看其他的? 如果你回答【好喔】,那你就踩進去這個陷阱了。他會把他手上有的租不出去的爛房子都給你看(網上也有人說他會假裝又有房子被租走了,來讓你趕快決定),然後很多人就因為「已經來了,不能空手而回」的想法而租到爛的物件。所以一般來說除非你真的沒辦法多來幾次,而且對房子的品質沒甚麼考慮的話,否則一般都不推薦這個方法。 Facebook 租屋群組 這又是另一個大坑,裡面就是房仲跟騙子的聚集地。其中一個最大的群組就是這個: 這個群組上主要會看到三類帖子,第一類是房仲,第二類是騙子,第三類是房仲或是騙子裝的房東自租。 房仲 房仲就比較簡單,你看到有浮水印的,多數都是房仲所推出來的房子。一般來說都會附上房仲的登記號碼(我猜政府應該是有管理制度,可以查詢到該房仲是不是合法經營的)。一般來說透過房仲找的話對方會多收你租金的 50% 作為中介費(一次性),所以如果你是很忙沒時間找而且不怕價格偏高的話,透過房仲來找房也不免是一個快捷省時間的方法。至於哪家房仲或是不動產公司比較好,這個你可以參考台灣 ptt 或是 dcard 上面的帖文。 浮水印例子(一) 浮水印例子(二) 網絡騙子 騙子的概念大概就是要來騙你訂金的,因為通常這類帖子都是由機械人或是詐騙集團發出來,背後大概有個模板所以就比較好分辨了。如果你看到類似這種的,有很大機率都是假的租房帖文。 關閉留言功能的 要求「優質房客」 房東自租 這個就要特別注意了。雖然說確實會有機會是有房間是由房東自己釋出,但是更多時候是房仲騙你去看房或是騙子的技巧。例如說這種的,資料看上去是真的可是把回應功能關掉的;或是寫著「可預訂」的,一般都是假的。 一般來說房東自租的物件真的超級少,你要到當地掃街才會有機會找到,所以在 FB 或是 ptt 上能找到的,即使真的是房東自租,你也不應該去看(價格不合理、環境差等而導致沒人租)。 本地的不動產 當你人已經去到新竹後,你可以選擇到附近的不動產門市看看。一般來說跟你在 FB 上看到的差不多,但是沒有了騙子與房東自租的部分,所以基本上貼在店外的就是 What you see is what you get 類房間。如果有看到有興趣、價錢 ok 的話直接進去說要看房就好了。然而,在科學園區工作了好幾年的學長卻建議不要找這種的,風險很高(我也不太懂),但是基於前人經驗之談,我在這次找房間的時候就直接跳過這個選項。 掃街 最後我決定直接掃街,從金山前到後每條巷子都走一趟。這邊的文化我覺得比較有趣的是,很多房子外面都貼著「套房出租」的 banner。 這種打電話過去有機會是房仲,也有可能是房東,這完全要看你運氣;有時候打電話過去會被告知已經全租出了,也有可能是同一個房仲在其他地方會有其他房間等等的。再加上這附近的人口超多流動率也有夠高,根據我的抽樣統計在我找房子的這個月這附近大約有 23 - 25 間空房出現(以及被租走),所以如果第一天找不到的話可以多待幾天再找,到時候可能就能找到了。 關於外國人租房的這回事 基本想如果你想在科學園附近租房的話,有兩樣東西是必要的 園區內公司之工作證件或報到通知單居留證(部分房東對於外國人特別抗拒,所以這個也很看運氣)或 台灣人當保證人 第一項這倒是不大問題(你要是沒在園區找到工作,搬過去幹麼?),問題是第 2 跟 3 項。在我找房的過程中,遇到最多的是當房東或房仲知道我是香港人的時候,他就馬上拒絕租給我(大部分都是直接說不考慮租給外國人)。 另外我也有找到另一間房東自租的房子,房東是高雄人,人滿好的,可是聊了很久之後打算租下去的時候才跟我說需要保證人才能簽約。單單是這幾點我就完全感受到台灣老一輩人排外的文化( 所以簡單來說,如果你是本地人的話房子已經夠難找了,如果是外地(國)人的話這個就更難了。就我找房的這兩次新竹之旅來說,大概 70 - 80% 都是需要保證人 或是 不租外國人的,所以如果你真的像我這樣要在新竹找房子,記得一定要預留至少 4 - 5 天時間。 我的找房經歷 由於我還沒有考到機車駕照,在只有單車跟步行的情況下,我選擇了金山街一帶的房子。那邊在當地人口中被叫做「新手村」,作為「新手」,住「新手村」就最合適不過了。我第一次去那邊的時候是搭高鐵,到新莊車站之後走路到附近的公車(巴士)站,再轉科學園紅線進去科學園;面試完之後再經科學園路走到金山街。 之後第二次去新竹的時候因為不用去面試,可以直接去金山街,所以這次我選擇搭火車(台鐵) 到新莊車站 再走路過去。雖然說有一部分路上次有走過,想著應該後面那段都是差不多,結果走過光復路一段之後全是上坡,走到快要體力不支( 後來第二天繼續去掃街的時候我選擇直接搭去竹中車站,這樣走路的距離在地圖上看短了不少 中間一開始看到有一片綠色我還以為是一個公園,怎知道去到之後才發現是一個山坡(結果又是上坡,果然金「山」街真的在山上的啊) 這山坡大概有 4 - 5 層樓高,而且還要是這種小徑的樓梯有夠難走 還好走完這一次之後我終於找到合適的房子並下了訂金(下次去的時候應該就會直接「的士 ~」) 結論 如果你找到了在新竹科學園區的工作,雖然很恭喜你,但是最難的部分才剛開始。之後還要考慮到住宿、交通(聽說在新竹的話必須要懂得騎車,雖然說 Ubike 看上去也滿多的)跟食的問題(聽說除了火鍋跟 M 記以外都沒好吃的),所以適應力的技能點也要點好點滿才進去喔(
Recursion vs Dynamic Programming
剛剛我在寫 Leetcode 的 70. Climbing Stairs 沒留意到它是考 DP 寫成了 Recursion,結果跑起來 timeout。剛好我覺得這是一個很好的 Recursion vs DP 的範例,我就拿出來解釋一下好了。首先,這是我寫的 Recursion 解法: class Solution { public: int sol = 0; int climbStairs(int n) { f(n, 0); //把 sum 設為 0 return sol; } void f(int n, int sum){ if (sum > n) return; //超出了目標位置 if (sum == n){ sol++; //這是其中一個爬法 return; } //跟終點還有點距離,試試 f(n, sum+1); //爬一步 f(n, sum+2); //爬兩步 } }; 每爬一段之後,檢查是不是已經到終點了,如否,再分支出兩種走法 - 走一步 與 走兩步。 這個我覺得是最簡單易懂的寫法(甚至已經是教科書等級的 recursive function 了),但是居然跑到 timeout 這個我還真的沒想到。所以後來只好換成 space-time tradeoff 的 dynamic programming 寫法: class Solution { public: int climbStairs(int n) { int dp[46]; //因為題目說最多 n = 45 dp[1] = 1; //爬到第一格,只有一個爬法就是爬一格 dp[2] = 2; //爬到第二格,只有 1 + 1 跟 2 兩種爬法 for(int i = 3; i <= n; i++){ //從 3 開始,就是前兩格爬法的總和 dp[i] = dp[i-1] + dp[i-2]; } return dp[n]; // 最後回傳能爬到最後一格的總可能路線 } }; 果然是做嵌入式開發做得多,寫出來都是以最佳化 memory consumption 的職業病(
Leetcode 加速器 Cheat Code (C++)
最近因為工作面試的關系,我都在練習寫 Leetcode (畢竟從大學畢業之後我的主力語言都是 C (MCU / low level)、Go (Linux / Networking Applications)跟 網頁(HTML 、CSS 、JS 那堆)。雖然我都跟我面試的主管說「我都沒在寫 Leetcode」(因為我不想他們以「平常有寫多少 Leetcode 」為顧用考慮之一),但是或多或少因為有一段時間沒寫 C++ 了所以還是要找些免費題目來抓回以前的感覺。 最近我在寫 LeetCode 的時候發現一個滿有趣的現像,就是一般很多題目都會有一些特別特殊的極端例子(超快、或是超省記憶體)的情況。去翻一翻這些 code sample 之後我發現了一段很有趣的 code。 int speedUp = [] {std::ios::sync_with_stdio(0); std::cin.tie(0); return 0; }(); bool has[100002]; int digit(char c) { return c & 15; } bool isDigit(char c) { return '0' <= c && c <= '9'; } int init = [] { std::ofstream out("user.out"); std::cout.rdbuf(out.rdbuf()); for (string s; std::getline(std::cin, s); std::cout << '\n') { int n = count(s.begin(), s.end(), ',') + 3; memset(has, 0, n); for (int _i = 1, _n = s.length(); _i < _n; ++_i) { if (s[_i] == '-')for (_i += 2; isDigit(s[_i]); ++_i); else { int v = s[_i++] & 15; while (isDigit(s[_i])) v = v * 10 + digit(s[_i++]); if (0 < v && v < n) has[v] = true; } } for (int i = 1;; ++i) if (!has[i]) { std::cout << i; break; } } exit(0); return 0; }(); 沒錯,是一段 C++ 的 runtime cheat code。先上面的部分開始看,這邊它使用了 lambda function 來透過「假裝」定義 speedup variable 來執行了一些 STDIO redirect 的功能。我猜它應該是把之後執行的東西的 STDOUT 跟 parent 的 STDIN…
DIY 開源智能手錶(選螢幕篇)
最近因為各種原因想買一只智能(慧)手錶,可是由於我只喜歡方形的,所以剩下的選擇大概只有智慧手環、大號智慧手環跟 Apple Watch 可以選了。嘛,其實除了這些還有其他的啦,然而市場上就一堆很奇怪的選擇都沒一個完全對得上我想要的東西。 小米手環 8 Pro / 華為手環 8 :性價比跟功能都不錯,可是螢幕是像一般手環般長方形的不是方形Venu Sq 2:功能超棒,但是這個配色的品味只能說很獨特(?Fitbit (Google 旗下)Versa 4:這外形和黑邊遠看還以為是玩具Apple Watch SE:功能和外形都很棒,就是只支持 iOS 跟需要每天一充 說實話我覺得最好看的設計一定是屬於 Sony 的 Smartwatch 3 了。如果把這個設計換上最新的 AMOLED 螢幕和把那個用久了會黏手的 rubber wrist band 換成其他材質(如 silicon / TPU)就更完美了。 所以作為一個 DIY 愛好者,很自然的我會去想:假設我要自己做一只智能手錶的話,我會怎樣設計呢?首先,我想到的大概是以下的一些技術規格: 基於 ESP32 C3 的方案(在寫本文的時候最新支援 BLE + WiFi 的 MCU)AMOLED 螢幕 + 環境光傳感器(自動亮度調整)內置基本的傳感器(光學心跳 + 血氧、accelerometer(步數)、gyroscope(移動距離),如果有空位的話可以再塞個地磁 sensor 做指南針之類的) 然而有一些我也很想做的部分,是現在很多便宜的智能手錶沒有的 無線充電(對,現在很多大號手環還是在用磁石彈針來充電)Cloud First 資料傳輸(就是以 WiFi 為先,資料先到伺服器再同步到其他手機和裝置,有點像 ArozOS 的設計邏輯)開源、可由使用者自行編程(基於 ESP32 Arduino 內核 + LVGL?) 螢幕選擇 要做到上面的東西其實不難,最難的部分應該說是螢幕。螢幕這東西不像 電路板 可以隨便畫一畫送出去就能做,而是需要很大量的定制才會有這樣的產品。所以在設計這只手錶的電路板之前,就要先決定好手錶的外殼大小,而外殼大小就是取決於螢幕大小。 我覺得我也不是第一個遇到這樣困難的開發者。例如說著名的 Pebble 智能手錶之所以是長這樣是完全因為生產它的公司被 Sharp 生產的 Memory LCD display (1.33 inch)所限制,所以只能夠圍繞著這個當時唯一一片 high refresh rate 又省電的黑白螢幕來開發整個產品。 初代 Pebble 智能手錶 SHARP 1.33 inch Memory LCD display 雖然說現在很多智能手錶的出現而讓市場上多了很多 1.3 - 1.9 寸的螢幕,但是這又產生出另一堆問題: 便宜的智能手錶很多時候都會使用 TFT (IPS)螢幕大部分現有的 AMOLED 螢幕都是圓的很多做螢幕的廠商都只做螢幕、做電容觸控層的都只做觸控層、要找到非現有產品在用的螢幕總成很困難 單單是後兩個問題就把我能選擇的螢幕大小限制住了。把圓的 AMOLED 跟沒有觸控的去除掉之後,剩下的就只有 1.69 inch ,在開源 smartwatch project 很常見到的 waveshare 模組以外就差不多沒其他選擇了。 不過不得不說,這 1.69 雖然在黑色背景的畫面下看上去還不錯,但是一但亮起來你就會發現它黑邊粗得要死,還要上下闊度不同!(噴血 AMOLED 螢幕最佳選 然後就到這個,這是我在某中國的螢幕供應商那邊看到這片要價快 6 美元的 1.78 寸 AMOLED 螢幕,368 * 448 pixel https://www.dwo.net.cn/pd.jsp?id=11921 這個數字是不是有點眼熟,沒錯,這就是 Apple Watch SE 44mm 在用的那一片螢幕( 上下黑邊總於是一樣的了 只是說這片螢幕要用 ESP32 驅動會有點小問題:這東西只支援 SPI、QSPI 跟 MIPI DSI。MIPI DSI 先不用說了(這東西要用 arm 處理器才推得動),剩下的只有 SPI 跟 QSPI 能用。SPI 的問題在於即使是 ESP32 用 SPI half_speed (最高 80Mhz,但是考慮到 FreeRTOS 後面還有其他東西要跑,先把一半的 CPU cycle 預留給其他 vTasks 好了)速度來驅動它,你還是會看到動畫像 ppt 一樣(我猜的),但是我把計算資料塞給 chatGPT 後它也告訴我大概只能跑到 15.16 FPS,所以應該是肉眼可見的卡(? 這樣我就剩下 QSPI 可以選擇了。QSPI 大概就是拿四個針腳來推資料,理論上應該會快上不少。ChatGPT 說大概能推到 60fps,但是我先保守估計抓個 30…
Golang trie tree IP 轉國碼(Country Code)
最近因為有人 report 說 Zoraxy 的 GeoIP 功能不準,於是我在重新 review 這個 geoip database module 的時候發現:欸幹真的寫錯了欸!? 然後在花了一整個晚上 debug 之後,我終於找到正確的演算法和做法了。這裡為了省下後人在掘怎樣用 Golang 寫 IP 轉 Country Code 的 resolver,我這裡給大家一個快速上手的教學。 甚麼是 IP 轉 CC? IP 就是 IP 地址,CC 是 country code,有時稱為 ISO 國碼,例如說 HK TW US GB 之類的。IPv4 跟 v6 的地址區間通常會被 assign 到某一個國家的 ISP 上,而造成說只要知道 IP 地址,某程度上你可以知道那個 request 是從哪個國家出來的(先不要說 VPN 或是 ip spoofing 之類的部分的話)。可是由於每隔一段時間就會有 ISP 倒閉、收購或是重組之類的,所以 IP 轉 CC 的可靠度並非 100% 準確,這個時候我們就需要從一個可靠的來源來定期更新這一個 IP range 到 CC 的 mapping database 了。 這個 database 最出名的應該就是 maxmind 的 geoip database,人家都整理好所有數據到一個 database 檔案裡面,也提供 api 讓你可以快速查詢 cc,很多現代只會拉 library 的開發者通常就會直接選用它。然而,基於它的 license 問題,如果真接用它的方案的話會讓授權和 licensing 變得一團亂,所以基於多種考慮之下這個方案我們最終沒有使用,而是使用 Public domain 或 CC-0 的資料來源。 GeoIP Data Source 最後我們使用了來自 GeoFeed + Whois + ASN (CC-0)的資料來源: https://github.com/sapics/ip-location-db/tree/main/geolite2-country 問題是它的資料表是一個 csv 檔案,大概長這樣: 1.0.0.0,1.0.0.255,AU 1.0.1.0,1.0.3.255,CN 1.0.4.0,1.0.7.255,AU 1.0.8.0,1.0.15.255,CN 1.0.16.0,1.0.31.255,JP 1.0.32.0,1.0.63.255,CN 1.0.64.0,1.0.127.255,JP 1.0.128.0,1.0.255.255,TH 1.1.0.0,1.1.0.255,CN 1.1.1.0,1.1.1.255,AU 1.1.2.0,1.1.63.255,CN 1.1.64.0,1.1.127.255,JP .... 223.255.248.0,223.255.251.255,HK 223.255.252.0,223.255.253.255,CN 223.255.254.0,223.255.254.255,SG 223.255.255.0,223.255.255.255,AU 那假設我給你一個 ip 地址:A.B.C.D,你要怎樣把 country code 從 csv 裡面抓出來呢? 最直觀的答案:O(n) 當然如果會一點寫程式的人就會想到:那我把整個 csv loop 一次,看看哪一行的 start ip 跟 end ip 是包含我這個 ip 地址的就好了啦?這個的確是我們做給嵌入式裝置的做法(不過是為了省 memory),原理大概這樣: func isIPv4InRange(startIP, endIP, testIP string) (bool, error) { start := net.ParseIP(startIP) end := net.ParseIP(endIP) test := net.ParseIP(testIP) if start == nil || end == nil || test == nil {…
淺談 CSRF 與 CSRF Middleware
最近在我的 Zoraxy 開源專案那邊出現了這樣的一個 bug issue 他還很好的附了一個網頁測試介面的截圖 甚麼是 CSRF 攻擊? CSRF(Cross-Site Request Forgery,跨站請求偽造)是一種網絡攻擊技術,攻擊者通過在受害者不知情的情況下,冒充受害者的身份在受害者已經認證的網站上執行未經授權的操作。 CSRF 攻擊的工作原理 受害者登錄到可信網站:受害者首先登錄到一個需要認證的網站,在這例子是 Zoraxy 的後台管理頁面攻擊者製造惡意請求:攻擊者創建一個包含惡意請求的網頁或電子郵件,例如說這裡的一個 free burger 網頁 html + web form (action 或 form submit URL 指向 Zoraxy 的後台管理頁面)受害者訪問惡意鏈接:受害者在登錄狀態下 request 了攻擊者控制的網頁並提交了 web-form。執行未經授權的操作:由於受害者已經登錄(Auth Cookie 會跟著 form 一起送出去),Zoraxy webmin 介面會以為這些 request 是 valid 的,並執行相應的操作,例如上面例子的 toggle proxy 開關狀態 。 防禦 CSRF 攻擊的方法 CSRF Token:在每個受保護的操作請求中加入隨機生成的、唯一的 CSRF Token,並驗證這些 Token 以確保請求是合法的。檢查 Referer 和 Origin 頭:檢查 HTTP 頭中的 Referer 和 Origin 字段,確保請求是從合法的來源發出的。但是我在寫的是 Reverse proxy,本來 Referer 跟 origin 就是不可信的,所以不能使用此方法。雙重提交 Cookie:將 CSRF Token 存儲在 Cookie 中,並在請求中一起提交和驗證。使用 SameSite Cookie 屬性:設置 Cookie 的 SameSite 屬性為 Strict 或 Lax,限制跨站請求攜帶 Cookie。 說真的這也不是我第一次處理 csrf 問題。先前同一位使用者也對 ArozOS 提出過類似的 issue report。然而 ArozOS 因為是使用 agile 開發的,API 散落得到處都是,根本沒辦法用標準的方式來做 csrf middleware 來保護 API 接口,所以當時只用了一個非常不標準的方式來做(就是每一個 post request 之前都加一個要求 csrf token 的 ajax request,變成每個 request 都會變成要 request 2 次) csrf middleware csrf middleware 是一個 http Handler 並讓它來對 request 進行預處理並對不合規格的 POST (PUT / DELETE) 等 request 進行攔阻。所以這裡有兩個需要做的事情 在 HTML 頁面生成的時候,在頁面上加入一個 csrf token在有資料需要以 POST 等 request 回傳到伺服器時,在 payload 中以 header field 的方式附上頁面的 csrf token 在 ArozOS 的做法上,它是先對伺服器進行一個 csrf token generation request,然後再把 request 附到下一個 ajax POST request 裡面。這做法雖然不是不行,但是對於前端來說要改實在太複雜,而且一個 csrf token 又沒有那麼快 expire,不斷生的話只會浪費後端資源。 Production grade 的 csrf middleware 使用方法 由於 Zoraxy 的設計從一開始就按著標準的…
10 分鐘設定好 open web-ui 跟 ollama
open web-ui 是一個很方便的界面讓你可以像用 chat-GPT 那樣去跟 ollama 運行的模型對話。由於我最近收到一個 Zoraxy 的 bug report 指 open web-ui 經過 Zoraxy 進行 reverse proxy 之後出現問題,所以我就只好來裝裝看看並且嘗試 reproduce 出來了。 安裝 ollama 我這裡用的是 Debian,首先第一件事要做的當然就是安裝 ollama。教學在他們的網上有我這裡就直接寫 code 出來了。 https://ollama.com/download/linux curl -fsSL https://ollama.com/install.sh | sh 在執行這個 bash script 之後它會自動建立一個 systemd 的服務。預設 ollama 的 web server 只能透過 localhost loopback interface 存取,如果要透過其他網絡才能存取到 ollama 的 API 的話,我們就要讓它同時 listen to 其他的 network interface。最簡單直接的方式是把預設的 systemd service 檔案改成這樣: sudo systemctl stop ollama.service sudo systemctl edit ollama.service 然後在 service 檔案裡加入下面那行(見備注) ### Editing /etc/systemd/system/ollama.service.d/override.conf ### Anything between here and the comment below will become the new contents of the file # 加入下面這兩行 [Service] Environment="OLLAMA_HOST=0.0.0.0:11434" #下面的不要碰 ### Lines below this comment will be discarded ### /etc/systemd/system/ollama.service # [Unit] # Description=Ollama Service # After=network-online.target # # [Service] # ExecStart=/usr/local/bin/ollama serve # User=ollama # Group=ollama # Restart=always # RestartSec=3 # Environment="PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games" # # [Install] # WantedBy=default.target 儲存並退出之後重啟 ollama systemd 服務 sudo systemctl start ollama.service 抓模型 因為我比較喜歡用 CLI,所以我就直接透過 ssh 來順便把模型也載下來。這裡我在試玩的是 qwen:https://ollama.com/library/qwen 一般模型也會有不同大小,而我選這個是因為我要省空間(對,SSD 快要炸了),所以我就選了比較小的 1.8b 版本。你可以用這個指令來讓 ollama 準備這個模型: ollama run qwen:1.8b 如果要其他大小的模型的話,只要把後面的 1.8b 換成 4b / 7b 之類的就好了。另外比較有名的包含 llama3 之類也是可以透過這樣的方式下載。 值得一提 如果你的 root disk (Linux 的 / 或是 Windows 的 C: 硬碟)不夠空間跑你想測試的模型,你可以在啟動 ollama…
SEO 快速上手筆記
作為經營好幾個中小型的 Open Source Project website 的開發者,間中也會收到一些來自不同國家的 SEO 公司的垃圾郵件,說甚麼可以幫你把網站最佳化到上 Google 首頁結果之類的。首先,有幾點我要先說明 沒人知道 Google 的 Search Engine 排名演算法,而且很可能他們也是用 AI 來做的,他們自己的員工也不知道SEO 本身只是在 HTML file 的 head section 裡加一些 metadata 等級的簡單作業而已。就算要弄的話如果本身你會一點 HTML 跟知道要加甚麼 meta tag 的話其實也就一兩個小時的工作量就能做到外面公司的 80 - 90% 有效度。想在 Google 出第一個結果的最佳方法是幫你的產品改一個 Google Search 沒結果的名字(真的,不相信的話你去 Google 找 "Zoraxy" 看看,第一項應該就會出現我的 Zoraxy project 了,而那個 github page 根本除了 OG 以外沒做 SEO) 那正文要來了囉 HTML Head 結構 HTML Header 是一個簡單的 HTML tag。一般的話大概長這樣 <!DOCTYPE html> <html> <head> <!-- 這裡就是 Head 的部分--> </head> <body> </body> </html> 那一個完整,有做 SEO 的 head 會長怎樣呢?我們來看這個範例 <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="format-detection" content="telephone=no,email=no,address=no"> <title>托比的神奇蹦蹦主頁</title> <!-- HTML Meta Tags --> <title>imuslab</title> <meta name="description" content="創客產品與開源軟體項目,還有方便好用的各種網上小工具 | Archive of tobychui's maker products and open source projects, with online tools and service for myself but also for anyone who visited my site"> <!-- Facebook Meta Tags --> <meta property="og:url" content="https://imuslab.com"> <meta property="og:type" content="website"> <meta property="og:title" content="imuslab"> <meta property="og:description" content="創客產品與開源軟體項目,還有方便好用的各種網上小工具 | Archive of tobychui's maker products and open source projects, with online tools and service for myself but also for anyone who visited my site"> <meta property="og:image" content="https://imuslab.com/index/og.png"> <!-- Twitter Meta Tags --> <meta name="twitter:card"…
Zoraxy v3 – 適合新手的反向代理伺服器
最近我一直在進行相當多的 Zoraxy 更新。對於從未聽過 Zoraxy 的人來說,它是我的開源反向代理伺服器 (Reverse Proxy Server)。可以部署在你的 homelab 或server / NAS 裡面。 如果你對 Zoraxy 如何運作沒有興趣,單純是來找一個可以在 Windows 和 Linux 上運行的 Reverse Proxy Server,請按這裡。 v3 的新功能? 嗯,如果你之前使用過 Zoraxy v2,每次打開 Web 管理界面,你都會感到一陣沮喪的感覺。這是因為在開發 v2 的過程中,我沒有多餘的時間仔細考慮配色方案或設計一個具吸引力的界面配色(然而作為開源項目來說,這界面已經算是到達夠好 + 能用的設計)。 v2 homepage v2 的代理清單 現在 v3 長這個樣子 除了界面更新之外,Zoraxy 還添加了大量新功能,以 optimize proxy core 的功能(或者我可以說,因為有太多 user 是從 Nginx Proxy Manager(NPM)轉過來用 Zoraxy,所以有一些部分如果盡量接近 NPM 的設計會省下我很多處理 issue 的麻煩)。一般來說,這不是 NPM 的替代品,而更像是一個讓你更容易在測試和debug 時切換 endpoint 的 Reverse Proxy。但無論如何,我不介意人們提出感覺像 NPM 的 enhancement issues。 Default Site 在 Zoraxy v3 中,我們新增了對 Default Site 和多個 host name 的支援。 Zoraxy 的前身(名為“Web Proxy”,是 ArozOS 系統的一個子服務)設計僅用於處理單一域名的反向代理,v2 版本添加了對子域和其他主機名的支援,但由於是沒經過設計加進去的,所以對 user 來說有夠混亂( 使用者對 “Proxy ”、“Subdomain” 和 “Proxy Root” 的設定感到困惑然後又跑來開 issue。這就是為什麼在 v3 的設計中,我設計一個新的界面和設置邏輯。有了Default Site 的選項(對,就是 NPM 那個),那習慣用 NPM 的人可以輕鬆地切換到 Zoraxy 了。(順帶省下了我很多解釋東西怎樣用的時間) Default Site 提供 4 個選項供選擇。對於新手來說,你可能想要使用內部靜態網頁伺服器,在使用 "Static Web Server" 功能時將一個 "index.html" 文件作為你的網站首頁。這更像是傳統的 Apache 的用法,如果在 apache.conf 中沒有對應的 Virtual Host,所有的路由都會進入內置的靜態網頁伺服器,並從你的 /var/www/html 資料夾抓檔案來回應 request。 Redirection 和 404 Not Found 用起來也相對直接。對於 Redirection (重定向),你可以輸入要重定向的目標域名/IP 地址。在要將舊的(子)域名指向新域名,或者直接阻止那些未知/過時子域名的請求時滿有用的。 Wildcard 域名證書與 SNI 在 v3 中,我們 implement 了 TLS/SSL 證書查找邏輯中的 SNI。Zoraxy SNI 與其他 implementation 的區別在於,它不需要用戶輸入來將證書與特定主機名“鏈接”起來,而是會自動掃描並 serve 出對應的 TLS certificate。 在 v2 中,用戶需要手動為每個證書設置與匹配的域名。例如,如果你有一個證書可以涵蓋 a.example.com 和 b.example.com,你需要手動將其匹配關鍵字設置為 "example.com",才能使這個域名的 TLS 加密正常工作(不然會出現 self sign certificate error)。現在有了 Zoraxy v3 的自動證書查找邏輯,你不需要設定任何東西。只需上傳你的證書(或使用內置的 ACME 工具從你選擇的 CA 生成一個),Zoraxy 將自動解析並選擇正確的 certificate。唯一的限制是對於包含多個主機名的證書(例如 domain.com 和 anotherdomain.com),速度會稍微慢一些(可能需要 O(n)…
Concurrent & Racing Conditions (並發與競態條件)
最近因為我在開發 Zoraxy 時需要設計一個可以做到 high speed concurrent READ 跟間中 write 的 map,跟另一位高雄的工程師大佬討論了一個晚上,後來我發現原來滿多人對 concurrent / racing conditions 的處理有一定程度的誤解,所以我就來簡單的解釋一下最常見的誤解 - Check for locking。 問題 假設你有兩個 process,一邊要讀取一個無法同時讀寫的 map 資料結構(例如 Go 的 map[string]struct{}),另一個要寫入新的 key 到同一個 map。由於 map 並沒辦法處理 concurrent R/W,所以你要怎樣做呢? 解決方法 1:改用專用的 concurrent map (如 Go 的 sync.Map) 這個解決方法簡單直接,我就不再詳細解釋了。然而由於我在開發 Zoraxy 的 high concurrent access 特質,使用 sync.Map 的話會做成很高的 over-head,因此我並不是很想用它(但是也不影響 sync.Map 作為沒法解決時用的最後方案的決定) 解決方法 2:mutex lock 這算是比較正規的方案之一。在讀取和寫入之前先對 map (或是 slice) 進行鎖定,然後在寫入或讀取之後解鎖。這樣就能確保同時間只有一個 process 對這個 map 有 R/W access。但是這同時也會產生另一個問題,就是在 read lock 了之後,假設有另一個 process 也想同時進行讀取的話就必須要等前面的讀完,因此這個方案對於高並發的 request handling 來說並不是太有效率。 那聽到這裡的時候很多人會直覺的覺得:那只有 write 的時候 lock 就好啦,read 的時候檢查 map 是不是被 lock,沒 lock 的話就讀取。由於檢查的時候不會 lock,因此其他需要 read 的 process 也不會被擋不是嗎? 聽上去好像沒錯喔?讓我簡單寫一個 pseudocode 例子: function write(){ mutex.lock() map.write("foo", "bar") mutex.unlock() } function read(){ if (!mutex.isLocked()){ //Map is not locked var value = map.read("foo") print(value) }else{ //Map is locked, retry after 100ms delay(100) read() } } mutex 的部分可以是 programming language 自帶的 mutex 功能或是簡單的 boolean,效果都是一樣的。但是如果你有注意到,你不會找到 mutex.isLocked() 這個功能。 如果你需要用到這個,很大機會你寫錯了 這是為甚麼呢?那是因為 OS 層基本運作原理所引起的。這裡對於本科沒修 Computer Architecture (計算機架構)的工程師可能比較難理解。簡單來說就是,CPU 上就只有 n 個核心,但是你的作業系統上面有很多東西在執行,所以 OS 會對上面執行的工作(process)進行快速切換。每次切換的時候因為速度很快,所以會讓你有一種它們在並行處理的感覺。而實質上,每個 process switching 都會有一個 overhead,而這個 overhead 就是為甚麼 mutex lock 沒辦法被用於檢查的原因。 其中一種常用的 scheduling 演算法:Round Robin Process Switching 關於 CPU scheduling 跟 scheduling 演算法的詳情可以看這邊 所以為甚麼 Mutex 不能檢查是不是被其他 process 鎖上? 當你去檢查一個 mutex 是否被鎖的時候,檢查當下可能沒被鎖,但是檢查完到執行下一個邏輯的時候可能已經被另一個 process 鎖上了…