この記事を理解するには、SES を使用するための一定の基礎が必要です。理解できない場合は、この質問の説明を読んでください。
http://segmentfault.com/q/1010000000095210
SES の正式名は Simple Email Service で、Amazon が開始した基本的な電子メール サービスです。 AWS の基本サービスの一部として、AWS の伝統的な利点である安いを継承しています。
はい、本当に安いです。これが、私が mailgun やその他の優れた電子メール サービスを使用しない理由です。毎月 100,000 通の電子メールを送信する場合、基本的に支払う必要があるのは約 10 ドルだけです。数百ドルから始まることが多い他のサービスと比較すると、この価格のメリットは非常に大きいです。したがって、これで私はその多くの欠点を許容できます。
しかし、中国でSESを利用する人が増えるにつれ、昨年末、ある日突然ブロックされてしまい、致命傷となりました。そこで、このサービスを使い続けるために、自分の海外サーバーにプロキシを構築してみました。同時に、これは API を改善して、一斉メール送信などのより価値のある機能を実装する機会にもなります。
なので、海外サーバーを使用せずに直接リバースプロキシを作ってプレイしただけでは、表面的な問題は解決しただけで、機能拡張の必要性は実現されませんでした。したがって、この SES エージェントを設計するために 2 つの基本的な目標を設定しました
元の API インターフェイスと完全な互換性があるため、プロキシを使用するために元のコードを基本的に変更する必要はありません。
一斉メール機能の実装
最初のポイントを達成するのは実際には非常に簡単です。実際には、PHP を使用してリバース プロキシを実装し、送信されたパラメーターを受け取り、アセンブル後にcurl コンポーネントを使用して実際の SES サーバーに送信し、直接出力します。領収書を取得した後、クライアントに渡します。これは標準的な代理店のプロセスです。私のコードを以下に示します。重要な部分についてはコメントしました 。
これらのコードはドメイン名のルート ディレクトリに配置する必要があることに注意してください もちろん、第 2 レベルのドメイン名も使用できます。
- include __DIR__ . '/includes.php';
- // さらに重要なヘッダーがいくつかありますが、その他は注意する必要はありません
- $headers = array(
- 'Date : ' . get_header ('Date'),
- 'Host: ' . SES_HOST,
- 'X-Amzn-Authorization: ' . get_header('X-Amzn-Authorization')
- );
- // 次に、URL を再度アセンブルします。この正しい SES サーバーをリクエストするには
- $url = 'https://' . '/'
- . (empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING'] );
- $ch =curl_init();
- curl_setopt($ch, CURLOPT_USERAGENT, 'SimpleEmailService/php');
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
-
- // 処理が必要です `POST` メソッドと `DELETE` メソッドだけです。 `GET` メソッドはたくさんあるので、一つずつ実装しません
- // 実際には、これらはすべて取得するためのメソッドです。この情報はバックグラウンドで直接表示できます。
- switch ($_SERVER ['REQUEST_METHOD']) {
- case 'GET':
- Break;
- case 'POST':
- global $HTTP_RAW_POST_DATA;
- $data = empty( $HTTP_RAW_POST_DATA) ? file_get_contents('php://input')
- : $ HTTP_RAW_POST_DATA;
- $headers[] = 'Content-Type: application/x-www-form-urlencoded';
- parse_data($data);
- curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
- curl_setopt($ch , CURLOPT_POSTFIELDS, $data);
- ブレーク;
- case 'DELETE':
- curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
- ブレーク;
- デフォルト:
- Break;
- }
- curl_setopt($ch, CURLOPT_HTTPHEADER, $ headers);
- curl_setopt($ch, CURLOPT_HEADER, false);
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, curlopt_returntransfer、true);
- curl_setopt($ ch、curlopt_folowlocation、true); ;
- curl_close($ch);
- header('Content-Type : ' . $content_type, true, $status);
- echo $response;
- コードをコピー
このコードは非常にシンプルですが、注意する必要があるいくつかのトリックがあります。その中で、POST メソッドを処理するときに、この関数が実際に大量メールを実装するための鍵となります。
そう言えば、SES の電子メール送信 API について触れなければなりません。SES は、複数の送信オブジェクトをサポートする単純な電子メール送信 API のみを提供します。ただし、複数の受信者に送信する場合は、受信者列にも表示されます。他の受信者のアドレス。もちろん、ccやbccのカーボンコピー機能にも対応していますが、このカーボンコピー機能を使ってグループメールを送信すると、受信者には受信者ではなく、カーボンコピーの対象の中に含まれることになります。通常の Web サイトでは、これらは明らかに許容できません。
つまり、電子メールを送信するには、実際の同時インターフェースが必要です。SES によって割り当てられた割り当ては、1 秒あたり 28 件の電子メールを送信することであることを知っておく必要があります (各人に異なる割り当てが設定されています)。フル活用すると、1 時間あたり 100,000 件の電子メールを送信できるようになります。 、これは完全に中規模のウェブサイトのニーズを満たすことができます。
そこで私はアイデアを思いつきました。 クライアント インターフェイスをまったく変更せずに、プロキシ サーバー経由で送信された複数の受信者を含む電子メールを 1 つの受信者を含む複数の電子メールに解凍し、これらの電子メールを非同期を使用して SES に送信します。行列。これは parse_data 関数が行うことです。以下に、使用するすべてのプライベート関数を含むコードを直接記述します 。 - define('REDIS_HOST', '127.0.0.1');
- define('REDIS_PORT', 6379);
- define('SES_HOST', 'email.us-east-1.amazonaws .com');
- define('SES_KEY', '');
- define('SES_SECRET', '');
- /**
- * get_header
- *
- * @parammixed $name
- * @access public
- * @return void
- */
- function get_header($name) {
- $name = ' HTTP_' 。 strtoupper(str_replace('-', '_', $name));
- return isset($_SERVER[$name]) ? $_SERVER[$name] : '';
- }
- /**
- * my_parse_str
- *
- * @parammixed $query
- * @parammixed$params
- * @access public
- * @return void
- */
- function my_parse_str($query, &$params) {
- if (empty($query)) {
- return;
- }
-
- $decode = function ($str) {
- return rawurldecode(str_replace('~', '%7E', $str));
- };
- $data =explode('&', $query);
- $ params = array();
- foreach ($data as $value) {
- list ($key, $val) =explode('=', $value, 2);
- if (isset($params[$key]) ) {
- if (!is_array($params[$key])) {
- $params[$key] = array($params[$key]);
- }
- $params[$key][] = $val;
- } else {
- $params[$key] = $decode($val);
- }
- }
- }
- /**
- * my_urlencode
- *
- * @parammixed $str
- * @access public
- * @return void
- */
- function my_urlencode($str) {
- return str_replace('%7E ', '~', rawurlencode($str));
- }
- /**
- * my_build_query
- *
- * @parammixed $params
- * @access public
- * @return void
- */
- function my_build_query($parameters) {
- $params = array();
- foreach ($parameters as $var = > $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
- *
- * @parammixed $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;
- $headers[] = 'ホスト: ' 。 SES_HOST;
-
- $auth = 'AWS3-HTTPS AWSAccessKeyId=' 。 SES_KEY;
- $auth .= ',アルゴリズム=HmacSHA256,署名=' 。 $sig;
-
- $headers[] = 'X-Amzn-Authorization: ' . $auth;
- $headers[] = 'Content-Type: application/x-www-form-urlencoded';
-
- return $headers;
- }
-
- /**
- * parse_data
- *
- * @parammixed $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 関数が 2 番目の受信者から開始され、それらを個別のメールにまとめて、他の独立したプロセスが読み取って送信できるように Redis キューに入れていることがわかります。
最初の受信者から始めてみませんか?
元のプロトコルと互換性がある必要があるため、クライアントがメールリクエストを送信するときは、常に何かを返す必要があるため、最初の受信者のメールリクエストが直接送信されます。はキューに入らないので、実際の SES サーバーのレシートを取得してクライアントに返すことができ、クライアント コードは変更を加えずにこの戻りを処理できます。
SES メールに署名が必要な場合はどうすればよいですか?
はい、すべての SES メールには署名が必要です。したがって、解凍すると電子メールのデータが変更されるため、署名も変更する必要があります。 my_build_query 関数はこれを実行し、リクエスト パラメーターに再署名します。
以下は、このプロキシ システムの最後のコンポーネントであるメール送信キューの実装です。これも php ファイルであり、nohup php コマンドを使用して、独自のクォータ サイズに従ってバックグラウンドで複数の php プロセスを開始し、同時メールを実現できます。送信。その構造も非常にシンプルで、キュー内のメールを読んでから、curl を使用してリクエストを送信します
- include __DIR__ . '/includes.php';
- $redis = new Redis();
- $redis->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://' 。 /';
-
- $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 メール プロキシ サーバーの作成に関する私のアイデア全体です。ぜひ一緒に議論してください。
|