摘要:于是,我開始嘗試在境外自己的服務(wù)器上做一層代理來繼續(xù)使用這個(gè)服務(wù)。對(duì)于一個(gè)正規(guī)網(wǎng)站來說,這些顯然是不能容忍的。
看懂這篇文章需要你有一定的SES使用基礎(chǔ),如果你不明白,可以看這個(gè)問題里的討論 http://segmentfault.com/q/1010000000095210
SES的全稱是Simple Email Service,它是亞馬遜公司推出的一個(gè)郵件基礎(chǔ)服務(wù)。作為AWS基礎(chǔ)服務(wù)的一部分,它繼承了AWS的傳統(tǒng)優(yōu)勢(shì) -- 便宜。
是的,真的非常便宜。這就是為什么我沒用mailgun或者其它什么更牛逼郵件服務(wù)的原因。如果每月你發(fā)10萬封郵件的話,基本也只需要支付十多美刀左右。這和其它那些動(dòng)輒上百美刀起步的服務(wù)來說,價(jià)格優(yōu)勢(shì)很大。所以,憑著這個(gè)我也能忍受它的諸多缺點(diǎn)。
但是隨著國內(nèi)用SES的人增多,他在去年底的某一天突然被墻了,這可要了命了。于是,我開始嘗試在境外自己的服務(wù)器上做一層代理來繼續(xù)使用這個(gè)服務(wù)。同時(shí)這也提供了一個(gè)契機(jī),讓我可以有機(jī)會(huì)對(duì)它的api作出改進(jìn)來實(shí)現(xiàn)一些更有價(jià)值的功能,比如郵件群發(fā)。
因此我沒有用境外服務(wù)器直接做一個(gè)反向代理來玩,這樣只是解決了表面上的問題,但我擴(kuò)展功能的需求就不可能實(shí)現(xiàn)了。因此我為設(shè)計(jì)這個(gè)SES代理訂立了兩個(gè)基本目標(biāo)
完全兼容原有api接口,這意味著原有代碼基本不需要改變就可以用代理
實(shí)現(xiàn)郵件群發(fā)功能
實(shí)現(xiàn)第一點(diǎn)其實(shí)非常簡(jiǎn)單,其實(shí)就是用php實(shí)現(xiàn)了一個(gè)反向代理,把發(fā)送過來的參數(shù)接收到,然后組裝后使用curl組件發(fā)送給真正的SES服務(wù)器,取得回執(zhí)后再直接輸出給客戶端。這就是一個(gè)標(biāo)準(zhǔn)的代理流程,下面給出我的代碼,里面重要的部分我都給出了注釋
需要注意的是這些代碼需要放在域名的根目錄下,當(dāng)然二級(jí)域名也可以
這段代碼非常簡(jiǎn)單,但也有些技巧需要注意,其中我處理POST方法時(shí)使用了一個(gè)名為parse_data的私有函數(shù),這個(gè)函數(shù)實(shí)際上是實(shí)現(xiàn)群發(fā)郵件的關(guān)鍵。
說到這里我不得不提一下SES發(fā)郵件的API,SES只提供一個(gè)簡(jiǎn)單的郵件發(fā)送API,其中它的發(fā)送對(duì)象支持多個(gè),但當(dāng)你發(fā)送給多個(gè)收件人時(shí),它也會(huì)在收件人欄看到其他收件人的地址。當(dāng)然它也支持cc或者bcc的抄送功能,但當(dāng)你在使用這種抄送功能來實(shí)現(xiàn)群發(fā)郵件時(shí),收件者會(huì)看到自己是在抄送對(duì)象中,而不是在接收人中。對(duì)于一個(gè)正規(guī)網(wǎng)站來說,這些顯然是不能容忍的。
因此我們需要真正的并發(fā)接口來發(fā)送郵件,要知道SES分配給我的配額是每秒鐘可以發(fā)送28封郵件(每人配額不同),要是完全利用的話每小時(shí)可以發(fā)送10萬封郵件,完全可以滿足中型網(wǎng)站的需求了。
因此我產(chǎn)生了一個(gè)想法,在完全不改變客戶端接口的情況下,我在代理服務(wù)器上將發(fā)送過來的有多個(gè)收件人的一封郵件拆包成一個(gè)一個(gè)單個(gè)收件人的多封郵件,然后再將這些郵件用異步隊(duì)列的方式發(fā)送到SES上。這就是parse_data函數(shù)所做的事情,下面我直接給出includes.php里的代碼,這里包含了所有要用到的私有函數(shù),前面的define定義請(qǐng)根據(jù)自己的需求修改
$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); // 多個(gè)發(fā)送地址 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函數(shù)從第二個(gè)收件人開始,把它們組裝成一個(gè)一個(gè)多帶帶的郵件,放到redis隊(duì)列里,供其他獨(dú)立進(jìn)程讀取發(fā)送。
為什么不從第一個(gè)收件人開始?
因?yàn)橐嫒菰袇f(xié)議,客戶端發(fā)過來一個(gè)發(fā)郵件請(qǐng)求你總要給它返回一個(gè)東西吧,我又懶得偽造,因此它的第一個(gè)收件人的發(fā)郵件請(qǐng)求是直接發(fā)出去了,而并沒有進(jìn)入隊(duì)列,這樣我可以取得一個(gè)真實(shí)的SES服務(wù)器回執(zhí)返回給客戶端,客戶端代碼也無需做任何修改,就可以處理這個(gè)返回。
SES的郵件都是要簽名的怎么辦?
是的,所有的SES郵件都需要簽名。因此在你解包以后,郵件數(shù)據(jù)改變了,因此簽名也必須改變。my_build_query函數(shù)就是做這個(gè)事情的,它會(huì)對(duì)請(qǐng)求參數(shù)做重新簽名。
下面是這個(gè)代理系統(tǒng)的最后一個(gè)組成部分,郵件發(fā)送隊(duì)列實(shí)現(xiàn),它也是一個(gè)php文件,你可以根據(jù)自己的配額大小,在后臺(tái)用nohup php命令啟動(dòng)若干個(gè)php進(jìn)程,來實(shí)現(xiàn)并發(fā)郵件發(fā)送。它的結(jié)構(gòu)也非常簡(jiǎn)單,就是讀取隊(duì)列里的郵件然后用curl發(fā)送請(qǐng)求
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郵件代理服務(wù)器的整個(gè)思路,歡迎大家一同來探討。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/20607.html
摘要:注意如果圖片顯示不正常把這個(gè)改成。使用庫方式切換操作各個(gè)數(shù)據(jù)庫保存快照的頻率,第一個(gè)表示多長時(shí)間,第二個(gè)表示執(zhí)行多少次寫操作。在一定時(shí)間內(nèi)執(zhí)行一定數(shù)量的寫操作時(shí),自動(dòng)保存快照。保存快照是否使用壓縮數(shù)據(jù)快照文件名只是文件名,不包括目錄。 Nginx+Tomcat集群第三步(負(fù)載均衡+基于Spring Boot的Session共享) Nginx和Tomcat沒安裝好的可以參考前兩步: Ce...
閱讀 2723·2021-09-22 15:15
閱讀 815·2021-09-02 15:11
閱讀 2016·2021-08-30 09:48
閱讀 2059·2019-08-30 15:56
閱讀 1705·2019-08-30 15:52
閱讀 2238·2019-08-30 15:44
閱讀 586·2019-08-29 16:29
閱讀 1680·2019-08-29 11:06