【问题标题】:app engine incoming mail handling应用引擎传入邮件处理
【发布时间】:2011-04-03 18:46:39
【问题描述】:

我正在开发一个 Google App Engine 应用程序。
我希望在 '%username%@appid.appspotmail.com' 下接收邮件,其中 %username% 属于应用程序的用户。
我只是不知道在web.xml 文件中定义什么。
任何类似的解决方案,例如邮寄至:

  • '%username%.usermailbox@appid.appspotmail.com'
  • 'usermailbox.%username%@appid.appspotmail.com'

是可以接受的(如果使用通配符更容易)。

我已经尝试过(按照 Gopi 的建议)
将相关的 servlet 映射到 web.xml 文件中的 <url-pattern>/_ah/mail/user.*</url-pattern>。它不工作。
客户端收到退回消息,而服务器日志显示应用收到的相关请求,但被 404 拒绝。“没有处理程序匹配此 URL。” INFO 被添加到日志条目中。此外,在获取生成的 URL 时,我没有得到“此页面不支持 GET”,而是一个普通的 404。
但是,如果我发送邮件说“info@appid.appspotmail.com”,日志会显示 404(他们应该这样做,因为它没有在 web.xml 中映射)。此外,对于这样的请求,“没有处理程序匹配此 URL”。 INFO 被添加到相关的日志条目中。

不用说,在配置的服务下找到传入的邮件

【问题讨论】:

    标签: google-app-engine email web.xml


    【解决方案1】:

    我认为将类似于下面的条目放入您的 web.xml 应该可以匹配您的第二种情况 'usermailbox.%username%@appid.appspotmail.com

    <servlet>
      <servlet-name>handlemail</servlet-name>
      <servlet-class>HandleMyMail</servlet-class>
    </servlet>
    <servlet-mapping>
      <servlet-name>handlemail</servlet-name>
      <url-pattern>/_ah/mail/usermailbox.*</url-pattern>
    </servlet-mapping>
    

    【讨论】:

    • 这是客户端的退回消息,服务器日志上的 404。奇怪的是,我没有得到“没有与此 URL 匹配的处理程序。”。另外你我看到请求来自服务器自转发机制的IP:0.1.0.20。
    • 日志条目 #1. 08-25 03:25PM 40.743 /_ah/mail/user.david1@appid.appspotmail.com 404 13ms 19cpu_ms 0kb 查看详情 0.1.0.20 - - [25/Aug/2010:15:25:40 -0700] "POST /_ah/mail/user.david1@appid.appspotmail.com HTTP/1.1" 404 234 - - " appid.appspot.com" ms=13 cpu_ms=19 api_cpu_ms=0 cpm_usd=0.000947
    【解决方案2】:

    嗯...在尝试了所有可能的解决方案/网址映射后,我选择了一个又快又丑的解决方案。
    要点是拥有一个“包罗万象”的邮件 servlet,作为其他特定 servlet 的调度程序。它就像一个巨大的switch,其中参数是请求 URL。
    这不是我想要的,但它有效,而且似乎是唯一有效的。

    我有一个处理所有传入邮件的 servlet IncomingMail。期间。
    所以现在,/_ah/mail/ 下的唯一 URL 映射如下:

    <servlet>
        <servlet-name>IncomingMail</servlet-name>
        <servlet-class>IncomingMail</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>IncomingMail</servlet-name>
        <url-pattern>/_ah/mail/*</url-pattern>
    </servlet-mapping>
    

    此外,我还有以下 servlet,映射为“plain-old-servlet”:
    (注意&lt;url-pattern&gt;,不是“邮件映射”servlet)

    <servlet>
        <servlet-name>GetUserMail</servlet-name>
        <servlet-class>GetUserMail</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>GetUserMail</servlet-name>
        <url-pattern>/serv/userMail</url-pattern>
    </servlet-mapping>
    

    包罗万象的 servlet(最终会)看起来像一个巨大的开关:

    public class IncomingMail extends HttpServlet {
        private final String USER_MAIL_PREFIX="http://appid.appspot.com/_ah/mail/user.";
        private final String USER_MAIL_SERVLET="/serv/userMail";
        ...
        public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            String url = req.getRequestURL().toString();
            System.out.println("IncomingMail called, with URL: "+url);
            String email;
            String servlet;
    
            if (url.startsWith(USER_MAIL_PREFIX)) {
                email=url.replace(USER_MAIL_PREFIX, "");
                servlet=USER_MAIL_SERVLET;
            }//userMail 
            if (url.startsWith(OTHER_PREFIX)) {
                //Redirect to OTHER servlet
            }
            ...
            System.out.println("forward to '"+servlet+"', with email '"+email+"'");
            RequestDispatcher dispatcher=req.getRequestDispatcher(servlet);
            try {
                req.setAttribute("email", email);
                dispatcher.forward(req, resp);
            } catch (ServletException e) {              
                System.err.println(e);
            }           
    
        }
    }
    

    目标 servlet(本例中为GetUserMail)执行getRequestParameter("email"),以查看特定的目标邮箱。
    它将接收发送到“user.%un%@appid.appspotmail.com”的所有邮件,其中 %un% 是应用程序空间中的用户名。
    servlet 接收的电子邮件参数的格式为“%un%@appid.appspotmail.com”,不带识别前缀。
    每个这样的“特定”servlet 都会从邮件调度程序 servlet 中获得“它的一部分”,而 email 参数已经没有识别前缀。

    我将在安全性下添加一条注释:
    如果您担心对“特定 servlet”的虚假请求,只需将它们全部定义在您站点中的公共虚拟命名空间(例如 /servmail/)下,然后定义一个新的 &lt;security-constraint&gt; 以允许请求仅来自应用程序本身。
    像这样(在web.xml里面):

        <security-constraint>
            <web-resource-collection>
                <web-resource-name>MailServlets</web-resource-name>
                <description>policy for specific mail servlets</description>
                <url-pattern>/servmail/*</url-pattern>
            </web-resource-collection>
            <auth-constraint>
                <role-name>admin</role-name>
            </auth-constraint>
        </security-constraint>
    

    仍然希望听到有人尝试并成功进行了通配符 &lt;url-pattern&gt; 邮件映射,而不是包罗万象的映射。

    【讨论】:

      【解决方案3】:

      以下提供了一个合理的解释,感谢 url-pattern and wildcards 指的是 http://jcp.org/aboutJava/communityprocess/mrel/jsr154/index2.html(滚动到第 11.2 节)

      在 url 模式中,* 通配符的行为与人们假设的不同, 它被视为普通字符,除了 - 当字符串以 /* 结尾时表示“路径映射” - 或者它以 * 开头。对于“扩展映射”

      太糟糕了,将通配符匹配电子邮件收件人地址到不同的 servlet 会很好,如 Google 的 API 文档示例中所述。我现在使用的是绝对匹配,它不像 appid 需要包含的那样干净。

      【讨论】:

        【解决方案4】:

        我遇到了类似的问题(使用 Python,所以使用 yaml 配置文件而不是 XML),原因原来是因为我放了:

        - url: /_ah/mail/.+ 
          script: handle_incoming_email.py 
          login: admin
        

        在现有的全部条目之前:

        - url: /.*
          script: main.py
        

        这在发送测试消息时在服务器上产生 404 和“消息发送失败”。

        在 catch-all 条目解决问题后移动它。

        【讨论】:

          【解决方案5】:

          我很确定问题在于您尝试使用.*web.xml 中的 URL 表达式是 glob,而不是正则表达式,因此您应该只使用 * - .* 只会匹配以点开头的字符串。

          【讨论】:

            【解决方案6】:

            当 App Engine 开始使用真正的 Java Web 服务器时发生了这种变化(因此 Toby 的解释是正确的......遗憾的是,我似乎无法恢复我的登录来投票!)。我的建议是使用过滤器。在为 GAE 编写玩具应用程序时,我使用了下面的过滤器。一旦在本文末尾定义了基类,就可以创建一系列邮件处理程序(如下所示)。您所要做的就是在您的 web.xml 中注册每个过滤器来处理 /_ah/mail/*。

            public class HandleDiscussionEmail extends MailHandlerBase {
            
              public HandleDiscussionEmail() { super("discuss-(.*)@(.*)"); }
            
              @Override
              protected boolean processMessage(HttpServletRequest req, HttpServletResponse res)
                throws ServletException 
              { 
                MimeMessage msg = getMessageFromRequest(req); 
                Matcher match = getMatcherFromRequest(req);
                ...
             }
            
            }
            

            public abstract class MailHandlerBase implements Filter {
            
              private Pattern pattern = null;
            
              protected MailHandlerBase(String pattern) {
                if (pattern == null || pattern.trim().length() == 0)
                {
                  throw new IllegalArgumentException("Expected non-empty regular expression");
                }
                this.pattern = Pattern.compile("/_ah/mail/"+pattern);
              }
            
              @Override public void init(FilterConfig config) throws ServletException { }
            
              @Override public void destroy() { }
            
              /**
               * Process the message. A message will only be passed to this method
               * if the servletPath of the message (typically the recipient for
               * appengine) satisfies the pattern passed to the constructor. If
               * the implementation returns <code>false</code>, control is passed
               * o the next filter in the chain. If the implementation returns
               * <code>true</code>, the filter chain is terminated.
               *
               * The Matcher for the pattern can be retrieved via
               * getMatcherFromRequest (e.g. if groups are used in the pattern).
               */
              protected abstract boolean processMessage(HttpServletRequest req, HttpServletResponse res) throws ServletException;
            
              @Override
              public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain chain)
                  throws IOException, ServletException {
            
                HttpServletRequest req = (HttpServletRequest) sreq;
                HttpServletResponse res = (HttpServletResponse) sres;
            
                MimeMessage message = getMessageFromRequest(req);
                Matcher m = applyPattern(req);
            
                if (m != null && processMessage(req, res)) {
                  return;
                }
            
                chain.doFilter(req, res); // Try the next one
            
              }
            
              private Matcher applyPattern(HttpServletRequest req) {
                Matcher m = pattern.matcher(req.getServletPath());
                if (!m.matches()) m = null;
            
                req.setAttribute("matcher", m);
                return m;
              }
            
              protected Matcher getMatcherFromRequest(ServletRequest req) {
                return (Matcher) req.getAttribute("matcher");
              }
            
              protected MimeMessage getMessageFromRequest(ServletRequest req) throws ServletException {
                MimeMessage message = (MimeMessage) req.getAttribute("mimeMessage");
                if (message == null) {
                  try {
                    Properties props = new Properties();
                    Session session = Session.getDefaultInstance(props, null);
                    message = new MimeMessage(session, req.getInputStream());
                    req.setAttribute("mimeMessage", message);
            
                  } catch (MessagingException e) {
                    throw new ServletException("Error processing inbound message", e);
                  } catch (IOException e) {
                    throw new ServletException("Error processing inbound message", e);
                  }
                }
                return message;
              }
            
            
            
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2010-10-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-07-21
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多