이 기사를 이해하려면 SES 사용에 대한 특정 기초가 필요합니다. 이해가 되지 않으면 이 질문의 토론을 읽어보세요.
http://segmentfault.com/q/1010000000095210
SES의 정식 명칭은 Simple Email Service로, Amazon에서 출시한 기본 이메일 서비스입니다. AWS 기본 서비스의 일부로 AWS의 전통적인 장점인 저렴함을 계승합니다.
네, 정말 저렴해요. 이것이 내가 mailgun이나 다른 멋진 이메일 서비스를 사용하지 않는 이유입니다. 한 달에 100,000통의 이메일을 보낸다면 기본적으로 10달러 정도만 지불하면 됩니다. 종종 수백 달러부터 시작하는 다른 서비스와 비교할 때 이러한 가격 이점은 엄청납니다. 그러므로 이것으로 나는 그것의 많은 단점을 견딜 수 있습니다.
그런데 중국에서 SES를 이용하는 사람이 늘어나자 지난해 말 어느 날 갑자기 차단당해 치명적인 일이 발생했다. 그래서 이 서비스를 계속 이용하기 위해 해외 서버에 프록시를 구축하기 시작했습니다. 동시에 이는 대량 메일 발송과 같은 좀 더 가치 있는 기능을 구현하기 위해 API를 개선할 수 있는 기회도 제공합니다.
따라서 해외 서버를 사용하여 역방향 프록시를 직접 만들어 플레이하지 않았습니다. 이는 표면적인 문제만 해결했을 뿐 기능 확장에 대한 요구를 충족할 수는 없었습니다. 그래서 저는 이 SES 에이전트를 설계하기 위해 두 가지 기본 목표를 세웠습니다
원본 API 인터페이스와 완벽하게 호환됩니다. 즉, 기본적으로 프록시를 사용하기 위해 원본 코드를 변경할 필요가 없습니다.
대량메일 발송 기능 구현
첫 번째 요점을 달성하는 방법은 실제로 매우 간단합니다. 실제로 PHP를 사용하여 역방향 프록시를 구현하고 전송된 매개변수를 받은 다음 어셈블리 후 컬 구성요소를 사용하여 실제 SES 서버로 전송하는 것입니다. 클라이언트에 직접 출력합니다. 이는 표준 대행사 프로세스이며 아래에 내 코드가 제공되며 중요한 부분을 설명했습니다
이러한 코드는 도메인 이름의 루트 디렉터리에 있어야 합니다. 물론 2차 도메인 이름도 사용할 수 있습니다.
- include __DIR__ .
- // 다음은 몇 가지 중요한 헤더입니다. 아니요 주의가 필요합니다
- $headers = array(
- 'Date: ' . get_header('Date'),
- 'Host: ' . SES_HOST,
- 'X-Amzn-Authorization: ' . get_header ( 'X-Amzn-Authorization')
- );
- // 그런 다음 URL을 다시 조합하여 올바른 SES 서버를 요청합니다
- $url = 'https://'/. '
- . (비어 있음($_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 = 비어 있음($HTTP_RAW_POST_DATA) ? file_get_contents ('php://input')
- : $HTTP_RAW_POST_DATA;
- $headers[] = '콘텐츠 유형: application/x-www -form-urlencoded';
- parse_data($data);
- curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
- curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
- break;
- 케이스 'DELETE':
- cur_setopt($ ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
- break;
- 기본값:
- break;
- }
- 컬_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_FOLLOWLOCATION, true) ;
- $response =curl_exec($ch);
- $content_type =curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
- $ status =curl_getinfo($ch, CURLINFO_HTTP_CODE);
- curl_close($ch);
- header('Content-Type: ' . $content_type, true, $status);
- echo $response;
코드 복사 이 코드는 매우 간단하지만 주의해야 할 몇 가지 트릭이 있습니다. 그 중 POST 메서드를 처리할 때 pars_data라는 비공개 함수를 사용했는데, 이 함수가 실제로 대량 메일링을 구현하는 데 핵심입니다.
SES 이메일 API에 대해 언급해야겠습니다. SES는 여러 전송 개체를 지원하는 간단한 이메일 전송 API만 제공합니다. 하지만 여러 수신자에게 보낼 경우에는 다른 수신자의 주소도 표시됩니다. 수신자 열. 물론 cc나 bcc의 카본 카피 기능도 지원하는데, 이 카본 카피 기능을 사용해 그룹 이메일을 보내면 수신자는 자신이 수신자 중이 아닌 카본 카피 개체 중에 있다는 것을 보게 됩니다. 일반 웹사이트의 경우 이는 분명히 용납할 수 없는 일입니다.
그래서 이메일을 보내려면 실제 동시 인터페이스가 필요합니다. SES에서 나에게 할당한 할당량은 초당 28개의 이메일을 보내는 것입니다(각 사람의 할당량은 다름). 완전히 활용하면 한 번에 100,000개의 이메일을 보낼 수 있습니다. hour. 이는 중간 규모 웹사이트의 요구 사항을 완벽하게 충족할 수 있습니다.
그래서 아이디어가 떠올랐습니다. 클라이언트 인터페이스를 전혀 변경하지 않고 여러 수신자가 보낸 이메일을 보낸 사람이 보낸 여러 이메일을 프록시 서버에서 단일 수신자로 압축을 풀고 이러한 이메일을 보냅니다. 비동기 대기열 에서 SES로. 이것이 pars_data 함수가 하는 일입니다. 아래에서는 사용할 모든 비공개 함수가 포함된 코드를 include.php에 직접 제공합니다. 필요에 따라 이전 정의를 수정하세요. - define('REDIS_HOST', '127.0.0.1');
- define('REDIS_PORT', 6379);
- 정의('SES_HOST', 'email.us-east-1.amazonaws.com');
- define('SES_KEY', '');
- define('SES_SECRET', '');
-
- /**
- * get_header
- *
- * @param 혼합 $name
- * @access public
- * @return void
- */
- function get_header($name) {
- $name = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
- return isset($_SERVER[$name]) ? $_SERVER[$name] : '';
- }
- /**
- * my_parse_str
- *
- * @param 혼합 $query
- * @param 혼합 $params
- * @access public
- * @return void
- */
- function my_parse_str($query, &$params) {
- if (empty($ query)) {
- return;
- }
- $decode = function ($str) {
- return rawurldecode(str_replace('~', '~', $str));
- };
- $data =Explode('&', $query);
- $params = array();
- foreach($data를 $value로) {
- list( $key, $val) = 폭발('=', $value, 2);
- if (isset($params[$key])) {
- if (!is_array($params[$key]) ) {
- $params[$key] = 배열($params[$key]);
- }
- $params[$key][] = $val;
- } else {
- $params[$key] = $decode($val);
- }
- }
- }
- /**
- * my_urlencode
- *
- * @param 혼합 $str
- * @access public
- * @return void
- */
- function my_urlencode($str) {
- return str_replace('~', '~', rawurlencode($str));
- }
- /**
- * my_build_query
- *
- * @param 혼합 $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
- *
- * @param 혼합 $headers
- * @access public
- * @return void
- */
- function my_headers() {
- $date = gmdate('D, d M Y H:i:s e');
- $sig = base64_encode(hash_hmac('sha256', $ 날짜, SES_SECRET, true));
-
- $headers = array();
- $headers[] = '날짜: ' . $date;
- $headers[] = '호스트: ' . SES_HOST;
-
- $auth = 'AWS3-HTTPS AWSAccessKeyId=' . SES_KEY;
- $auth .= ',Algorithm=HmacSHA256,Signature=' . $sig;
-
- $headers[] = 'X-Amzn-인증: ' . $auth;
- $headers[] = '콘텐츠 유형: 애플리케이션/x-www-form-urlencoded';
-
- return $headers;
- }
-
- /**
- * 파싱 데이터
- *
- * @param 혼합 $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 프로세스를 시작할 수 있습니다. 동시 메일 발송 . 구조도 매우 간단합니다. 대기열에 있는 이메일을 읽은 다음 컬을 사용하여 요청을 보내는 것입니다
- include __DIR__ .
- $redis = new Redis();
- $ redis->connect(REDIS_HOST, REDIS_PORT);
-
- do {
- $pop = $redis->brPop('mail', 10);
- if (empty($pop)) {
- 계속;
- }
-
- list ($k, $id) = $pop;
- list($aKey, $mKey) =Explode('|', $id);
-
- $address = $redis->hGet('a:' . $mKey, $aKey);
- if (empty($address)) {
- 계속;
- }
-
- $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 = 컬_init();
- 컬_setopt($ch, CURLOPT_USERAGENT, 'SimpleEmailService/php') ;
- 컬_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
- 컬_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
-
- 컬_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
- 컬_setopt( $ch, CURLOPT_RETURNTRANSFER, true);
- 컬_setopt($ch, CURLOPT_POSTFIELDS, $data);
- 컬_setopt($ch, CURLOPT_HTTPHEADER, $headers);
- 컬_setopt($ch, CURLOPT_URL, $url);
- 컬_setopt($ch, CURLOPT_TIMEOUT, 10);
-
- 컬_exec($ch);
- 컬_닫기($ch);
-
- 설정 해제($ch);
- 설정 해제 ($data);
-
- } while (true);
-
-
코드 복사
위 내용은 SES 메일 프록시 서버 작성에 대한 전체 아이디어입니다. 모두 함께 토론해 보세요.
|