Use PHP to implement an AmazonSES proxy server

Source: Internet
Author: User
Tags define definition
Use PHP to implement an AmazonSES proxy server

Understanding this article requires you to have a certain foundation of SES, if you do not understand, you can see the discussion of http://segmentfault.com/q/1010000000095210 in this question

SES is short for Simple Email Service, which is a Mail basic Service launched by Amazon. As part of AWS basic services, it inherits the traditional advantages of AWS --Cheap.

Yes, it's really cheap. This is why I didn't use mailgun or other services that are even more powerful. If you send 0.1 million emails each month, you only need to pay about 10 million USD. This is a great price advantage for other services that start with hundreds of US dollars. Therefore, I can bear many shortcomings of it.

However, as the number of people using SES in China increases, he was suddenly attacked at the end of last year. As a result, I began to try to use this service as a proxy on my own servers outside China. At the same time, this also provides an opportunity for me to improve its api to implement some more valuable functions, such as sending emails.

Therefore, I didn't use a foreign server to directly create a reverse proxy to play the game. this only solves the superficial problem, but I cannot implement the function expansion requirements. So I set two basic goals for designing this SES proxy

Fully compatible with the original api interface, which means that the original code can be used as a proxy to implement the Mail group function without any changes.

The first implementation is actually very simple. In fact, php is used to implement a reverse proxy, receive the sent parameters, and then assemble them and send them to the real SES server using the curl component, obtain the receipt and then directly output it to the client. This is a standard proxy process. my code is given below, and I have commented on all the important parts in it.

Note that these codes need to be placed under the root directory of the domain name. of course, the second-level domain name can also

  1. Include _ DIR _. '/includes. php ';
  2. // Here are several important headers, which do not need to be followed.
  3. $ Headers = array (
  4. 'Date: '. get_header ('date '),
  5. 'Host: '. SES_HOST,
  6. 'X-Amzn-Authorization: '. get_header ('x-Amzn-authorization ')
  7. );
  8. // Then assemble the url again to request this positive SES server
  9. $ Url = 'https: // '. SES_HOST .'/'
  10. . (Empty ($ _ SERVER ['query _ string'])? '':'? '. $ _ SERVER ['query _ string']);
  11. $ Ch = curl_init ();
  12. Curl_setopt ($ ch, CURLOPT_USERAGENT, 'simpleemailservice/php ');
  13. Curl_setopt ($ ch, CURLOPT_SSL_VERIFYHOST, 1 );
  14. Curl_setopt ($ ch, CURLOPT_SSL_VERIFYPEER, 1 );
  15. // The 'post' and 'delete' methods need to be processed. I will not implement the 'get' methods one by one.
  16. // Actually, they are all methods for obtaining the current information. you can view the information directly in the background.
  17. Switch ($ _ SERVER ['request _ method']) {
  18. Case 'get ':
  19. Break;
  20. Case 'Post ':
  21. Global $ HTTP_RAW_POST_DATA;
  22. $ Data = empty ($ HTTP_RAW_POST_DATA )? File_get_contents ('php: // input ')
  23. : $ HTTP_RAW_POST_DATA;
  24. $ Headers [] = 'content-Type: application/x-www-form-urlencoded ';
  25. Parse_data ($ data );
  26. Curl_setopt ($ ch, CURLOPT_CUSTOMREQUEST, 'post ');
  27. Curl_setopt ($ ch, CURLOPT_POSTFIELDS, $ data );
  28. Break;
  29. Case 'delete ':
  30. Curl_setopt ($ ch, CURLOPT_CUSTOMREQUEST, 'delete ');
  31. Break;
  32. Default:
  33. Break;
  34. }
  35. Curl_setopt ($ ch, CURLOPT_HTTPHEADER, $ headers );
  36. Curl_setopt ($ ch, CURLOPT_HEADER, false );
  37. Curl_setopt ($ ch, CURLOPT_URL, $ url );
  38. Curl_setopt ($ ch, CURLOPT_RETURNTRANSFER, true );
  39. Curl_setopt ($ ch, CURLOPT_FOLLOWLOCATION, true );
  40. $ Response = curl_exec ($ ch );
  41. $ Content_type = curl_getinfo ($ ch, CURLINFO_CONTENT_TYPE );
  42. $ Status = curl_getinfo ($ ch, CURLINFO_HTTP_CODE );
  43. Curl_close ($ ch );
  44. Header ('content-Type: '. $ content_type, true, $ status );
  45. Echo $ response;

This code is very simple, but some tips need to be noted. I used a private function named parse_data when processing the POST method. This function is actually the key to implementing mass mail.

Speaking of this, I have to mention the SES mail API. SES only provides a simple mail sending API, which supports multiple sending objects, but when you send to multiple recipients, it also displays the addresses of other recipients in the recipient column. Of course, it also supports cc or bcc cc, but when you use this cc function to send mass mails, the recipient will see that they are in the cc object, not in the recipient. For a regular website, these are obviously intolerable.

Therefore, we need a real concurrent interface to send emails. we need to know that the quota assigned to me by SES is that 28 emails can be sent per second (different quotas per person ), if it is fully utilized, you can send 0.1 million emails per hour to meet the needs of medium-sized websites.

So I came up with an idea,Without changing the client interface at all, I split an email from multiple recipients into multiple emails from one single recipient on the proxy server, then, these emails are sent to SES in an asynchronous queue.. This is what the parse_data function does. next I will give the code in mongodes. php directly. This includes all the private functions to be used. please modify the previous define definition according to your needs.

  1. Define ('redis _ host', '2017. 0.0.1 ');
  2. Define ('redis _ port', 6379 );
  3. Define ('ses _ host', 'Email .us-east-1.amazonaws.com ');
  4. Define ('ses _ key ','');
  5. Define ('ses _ secret ','');
  6. /**
  7. * Get_header
  8. *
  9. * @ Param mixed $ name
  10. * @ Access public
  11. * @ Return void
  12. */
  13. Function get_header ($ name ){
  14. $ Name = 'http _ '. strtoupper (str_replace ('-',' _ ', $ name ));
  15. Return isset ($ _ SERVER [$ name])? $ _ SERVER [$ name]: '';
  16. }
  17. /**
  18. * My_parse_str
  19. *
  20. * @ Param mixed $ query
  21. * @ Param mixed $ params
  22. * @ Access public
  23. * @ Return void
  24. */
  25. Function my_parse_str ($ query, & $ params ){
  26. If (empty ($ query )){
  27. Return;
  28. }
  29. $ Decode = function ($ str ){
  30. Return rawurldecode (str_replace ('~ ',' % 7e', $ str ));
  31. };
  32. $ Data = explode ('&', $ query );
  33. $ Params = array ();
  34. Foreach ($ data as $ value ){
  35. List ($ key, $ val) = explode ('=', $ value, 2 );
  36. If (isset ($ params [$ key]) {
  37. If (! Is_array ($ params [$ key]) {
  38. $ Params [$ key] = array ($ params [$ key]);
  39. }
  40. $ Params [$ key] [] = $ val;
  41. } Else {
  42. $ Params [$ key] = $ decode ($ val );
  43. }
  44. }
  45. }
  46. /**
  47. * My_urlencode
  48. *
  49. * @ Param mixed $ str
  50. * @ Access public
  51. * @ Return void
  52. */
  53. Function my_urlencode ($ str ){
  54. Return str_replace ('% 7e ','~ ', Rawurlencode ($ str ));
  55. }
  56. /**
  57. * My_build_query
  58. *
  59. * @ Param mixed $ params
  60. * @ Access public
  61. * @ Return void
  62. */
  63. Function my_build_query ($ parameters ){
  64. $ Params = array ();
  65. Foreach ($ parameters as $ var => $ value ){
  66. If (is_array ($ value )){
  67. Foreach ($ value as $ v ){
  68. $ Params [] = $ var. '='. my_urlencode ($ v );
  69. }
  70. } Else {
  71. $ Params [] = $ var. '='. my_urlencode ($ value );
  72. }
  73. }
  74. Sort ($ params, SORT_STRING );
  75. Return implode ('&', $ params );
  76. }
  77. /**
  78. * My_headers
  79. *
  80. * @ Param mixed $ headers
  81. * @ Access public
  82. * @ Return void
  83. */
  84. Function my_headers (){
  85. $ Date = gmdate ('d, d m y h: I: s e ');
  86. $ Sig = base64_encode (hash_hmac ('sha256 ', $ date, SES_SECRET, true ));
  87. $ Headers = array ();
  88. $ Headers [] = 'date: '. $ Date;
  89. $ Headers [] = 'host: '. SES_HOST;
  90. $ Auth = 'aws3-HTTPS AWSAccessKeyId = '. SES_KEY;
  91. $ Auth. = ', Algorithm = HmacSHA256, Signature ='. $ sig;
  92. $ Headers [] = 'x-Amzn-Authorization: '. $ auth;
  93. $ Headers [] = 'content-Type: application/x-www-form-urlencoded ';
  94. Return $ headers;
  95. }
  96. /**
  97. * Parse_data
  98. *
  99. * @ Param mixed $ data
  100. * @ Access public
  101. * @ Return void
  102. */
  103. Function parse_data (& $ data ){
  104. My_parse_str ($ data, $ params );
  105. If (! Empty ($ params )){
  106. $ Redis = new Redis ();
  107. $ Redis-> connect (REDIS_HOST, REDIS_PORT );
  108. // Multiple sending addresses
  109. If (isset ($ params ['Destination. ToAddresses. member.2 ']) {
  110. $ Address = array ();
  111. $ MKey = uniqid ();
  112. $ I = 2;
  113. While (isset ($ params ['Destination. ToAddresses. member. '. $ I]) {
  114. $ AKey = uniqid ();
  115. $ Key = 'Destination. ToAddresses. member. '. $ I;
  116. $ Address [$ aKey] = $ params [$ key];
  117. Unset ($ params [$ key]);
  118. $ I ++;
  119. }
  120. $ Data = my_build_query ($ params );
  121. Unset ($ params ['Destination. ToAddresses. member.1 ']);
  122. $ Redis-> set ('M: '. $ mKey, my_build_query ($ params ));
  123. Foreach ($ address as $ k => $ ){
  124. $ Redis-> hSet ('A: '. $ mKey, $ k, $ );
  125. $ Redis-> lPush ('mail', $ k. '|'. $ mKey );
  126. }
  127. }
  128. }
  129. }

We can see that the parse_data function starts from the second recipient and assembles them into a separate email and puts them in the redis queue for reading and sending by other independent processes.

Why not start with the first recipient?

To be compatible with the original protocol, the client sends an email request and you always need to return something to it. I am too lazy to forge it. Therefore, the first recipient's email request is sent directly, but it does not enter the queue, so that I can get a real SES server receipt and return it to the client, and the client code can process the return without any modifications.

What should I do if all SES emails require signatures?

Yes. all SES emails require signatures. Therefore, after you unpack the package, the email data is changed, so the signature must also be changed. The my_build_query function is used to re-sign the request parameters.

The following is the last part of the proxy system, which is implemented by the Mail sending queue. it is also a php file. you can set the size according to your quota, start several php processes using the nohup php command in the background to implement and send emails. Its structure is also very simple, that is, reading emails in the queue and then sending requests with curl

  1. Include _ DIR _. '/includes. php ';
  2. $ Redis = new Redis ();
  3. $ Redis-> connect (REDIS_HOST, REDIS_PORT );
  4. Do {
  5. $ Pop = $ redis-> brPop ('mail', 10 );
  6. If (empty ($ pop )){
  7. Continue;
  8. }
  9. List ($ k, $ id) = $ pop;
  10. List ($ aKey, $ mKey) = explode ('|', $ id );
  11. $ Address = $ redis-> hGet ('A: '. $ mKey, $ aKey );
  12. If (empty ($ address )){
  13. Continue;
  14. }
  15. $ Data = $ redis-> get ('M: '. $ mKey );
  16. If (empty ($ data )){
  17. Continue;
  18. }
  19. My_parse_str ($ data, $ params );
  20. $ Params ['Destination. ToAddresses. member.1 '] = $ address;
  21. $ Data = my_build_query ($ params );
  22. $ Headers = my_headers ();
  23. $ Url = 'https: // '. SES_HOST .'/';
  24. $ Ch = curl_init ();
  25. Curl_setopt ($ ch, CURLOPT_USERAGENT, 'simpleemailservice/php ');
  26. Curl_setopt ($ ch, CURLOPT_SSL_VERIFYHOST, false );
  27. Curl_setopt ($ ch, CURLOPT_SSL_VERIFYPEER, false );
  28. Curl_setopt ($ ch, CURLOPT_CUSTOMREQUEST, 'post ');
  29. Curl_setopt ($ ch, CURLOPT_RETURNTRANSFER, true );
  30. Curl_setopt ($ ch, CURLOPT_POSTFIELDS, $ data );
  31. Curl_setopt ($ ch, CURLOPT_HTTPHEADER, $ headers );
  32. Curl_setopt ($ ch, CURLOPT_URL, $ url );
  33. Curl_setopt ($ ch, CURLOPT_TIMEOUT, 10 );
  34. Curl_exec ($ ch );
  35. Curl_close ($ ch );
  36. Unset ($ ch );
  37. Unset ($ data );
  38. } While (true );

The above is the overall idea of writing the SES mail proxy server. You are welcome to discuss it together.

Proxy server, PHP, Amazon

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.