摘要:于是,我開始嘗試在境外自己的服務器上做一層代理來繼續使用這個服務。對于一個正規網站來說,這些顯然是不能容忍的。
看懂這篇文章需要你有一定的SES使用基礎,如果你不明白,可以看這個問題里的討論 http://segmentfault.com/q/1010000000095210
SES的全稱是Simple Email Service,它是亞馬遜公司推出的一個郵件基礎服務。作為AWS基礎服務的一部分,它繼承了AWS的傳統優勢 -- 便宜。
是的,真的非常便宜。這就是為什么我沒用mailgun或者其它什么更牛逼郵件服務的原因。如果每月你發10萬封郵件的話,基本也只需要支付十多美刀左右。這和其它那些動輒上百美刀起步的服務來說,價格優勢很大。所以,憑著這個我也能忍受它的諸多缺點。
但是隨著國內用SES的人增多,他在去年底的某一天突然被墻了,這可要了命了。于是,我開始嘗試在境外自己的服務器上做一層代理來繼續使用這個服務。同時這也提供了一個契機,讓我可以有機會對它的api作出改進來實現一些更有價值的功能,比如郵件群發。
因此我沒有用境外服務器直接做一個反向代理來玩,這樣只是解決了表面上的問題,但我擴展功能的需求就不可能實現了。因此我為設計這個SES代理訂立了兩個基本目標
完全兼容原有api接口,這意味著原有代碼基本不需要改變就可以用代理
實現郵件群發功能
實現第一點其實非常簡單,其實就是用php實現了一個反向代理,把發送過來的參數接收到,然后組裝后使用curl組件發送給真正的SES服務器,取得回執后再直接輸出給客戶端。這就是一個標準的代理流程,下面給出我的代碼,里面重要的部分我都給出了注釋
需要注意的是這些代碼需要放在域名的根目錄下,當然二級域名也可以
這段代碼非常簡單,但也有些技巧需要注意,其中我處理POST方法時使用了一個名為parse_data的私有函數,這個函數實際上是實現群發郵件的關鍵。
說到這里我不得不提一下SES發郵件的API,SES只提供一個簡單的郵件發送API,其中它的發送對象支持多個,但當你發送給多個收件人時,它也會在收件人欄看到其他收件人的地址。當然它也支持cc或者bcc的抄送功能,但當你在使用這種抄送功能來實現群發郵件時,收件者會看到自己是在抄送對象中,而不是在接收人中。對于一個正規網站來說,這些顯然是不能容忍的。
因此我們需要真正的并發接口來發送郵件,要知道SES分配給我的配額是每秒鐘可以發送28封郵件(每人配額不同),要是完全利用的話每小時可以發送10萬封郵件,完全可以滿足中型網站的需求了。
因此我產生了一個想法,在完全不改變客戶端接口的情況下,我在代理服務器上將發送過來的有多個收件人的一封郵件拆包成一個一個單個收件人的多封郵件,然后再將這些郵件用異步隊列的方式發送到SES上。這就是parse_data函數所做的事情,下面我直接給出includes.php里的代碼,這里包含了所有要用到的私有函數,前面的define定義請根據自己的需求修改
$value) { if (is_array($value)) { foreach ($value as $v) { $params[] = $var."=".my_urlencode($v); } } else { $params[] = $var."=".my_urlencode($value); } } sort($params, SORT_STRING); return implode("&", $params); } /** * my_headers * * @param mixed $headers * @access public * @return void */ function my_headers() { $date = gmdate("D, d M Y H:i:s e"); $sig = base64_encode(hash_hmac("sha256", $date, SES_SECRET, true)); $headers = array(); $headers[] = "Date: " . $date; $headers[] = "Host: " . SES_HOST; $auth = "AWS3-HTTPS AWSAccessKeyId=" . SES_KEY; $auth .= ",Algorithm=HmacSHA256,Signature=" . $sig; $headers[] = "X-Amzn-Authorization: " . $auth; $headers[] = "Content-Type: application/x-www-form-urlencoded"; return $headers; } /** * parse_data * * @param mixed $data * @access public * @return void */ function parse_data(&$data) { my_parse_str($data, $params); if (!empty($params)) { $redis = new Redis(); $redis->connect(REDIS_HOST, REDIS_PORT); // 多個發送地址 if (isset($params["Destination.ToAddresses.member.2"])) { $address = array(); $mKey = uniqid(); $i = 2; while (isset($params["Destination.ToAddresses.member." . $i])) { $aKey = uniqid(); $key = "Destination.ToAddresses.member." . $i; $address[$aKey] = $params[$key]; unset($params[$key]); $i ++; } $data = my_build_query($params); unset($params["Destination.ToAddresses.member.1"]); $redis->set("m:" . $mKey, my_build_query($params)); foreach ($address as $k => $a) { $redis->hSet("a:" . $mKey, $k, $a); $redis->lPush("mail", $k . "|" . $mKey); } } } }可以看到parse_data函數從第二個收件人開始,把它們組裝成一個一個多帶帶的郵件,放到redis隊列里,供其他獨立進程讀取發送。
為什么不從第一個收件人開始?
因為要兼容原有協議,客戶端發過來一個發郵件請求你總要給它返回一個東西吧,我又懶得偽造,因此它的第一個收件人的發郵件請求是直接發出去了,而并沒有進入隊列,這樣我可以取得一個真實的SES服務器回執返回給客戶端,客戶端代碼也無需做任何修改,就可以處理這個返回。
SES的郵件都是要簽名的怎么辦?
是的,所有的SES郵件都需要簽名。因此在你解包以后,郵件數據改變了,因此簽名也必須改變。my_build_query函數就是做這個事情的,它會對請求參數做重新簽名。
下面是這個代理系統的最后一個組成部分,郵件發送隊列實現,它也是一個php文件,你可以根據自己的配額大小,在后臺用nohup php命令啟動若干個php進程,來實現并發郵件發送。它的結構也非常簡單,就是讀取隊列里的郵件然后用curl發送請求
connect(REDIS_HOST, REDIS_PORT); do { $pop = $redis->brPop("mail", 10); if (empty($pop)) { continue; } list ($k, $id) = $pop; list($aKey, $mKey) = explode("|", $id); $address = $redis->hGet("a:" . $mKey, $aKey); if (empty($address)) { continue; } $data = $redis->get("m:" . $mKey); if (empty($data)) { continue; } my_parse_str($data, $params); $params["Destination.ToAddresses.member.1"] = $address; $data = my_build_query($params); $headers = my_headers(); $url = "https://" . SES_HOST . "/"; $ch = curl_init(); curl_setopt($ch, CURLOPT_USERAGENT, "SimpleEmailService/php"); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_exec($ch); curl_close($ch); unset($ch); unset($data); } while (true);以上就是我編寫SES郵件代理服務器的整個思路,歡迎大家一同來探討。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/20607.html
摘要:注意如果圖片顯示不正常把這個改成。使用庫方式切換操作各個數據庫保存快照的頻率,第一個表示多長時間,第二個表示執行多少次寫操作。在一定時間內執行一定數量的寫操作時,自動保存快照。保存快照是否使用壓縮數據快照文件名只是文件名,不包括目錄。 Nginx+Tomcat集群第三步(負載均衡+基于Spring Boot的Session共享) Nginx和Tomcat沒安裝好的可以參考前兩步: Ce...
閱讀 2376·2021-09-22 15:15
閱讀 640·2021-09-02 15:11
閱讀 1784·2021-08-30 09:48
閱讀 1884·2019-08-30 15:56
閱讀 1480·2019-08-30 15:52
閱讀 2042·2019-08-30 15:44
閱讀 431·2019-08-29 16:29
閱讀 1538·2019-08-29 11:06