App Engine でメイル受信

1.2.6で追加されたメイルの受信を試してみる.メイル受信機能は,XMPPの受信と同じでWeb hookで実現されている.つまり,メイルが来ると特定のURLに対するアクセスが発生して,それに対応するサーブレットが呼び出される.受信できるメイルアドレスは,

任意の文字列@アプリ名.appspotmail.com

appspot.com ではなく,appspotmail.com であるところに注意.ディスパッチされるURLは

/_ah/mail/アドレス

つまり

/_ah/mail/任意の文字列@アプリ名.appspotmail.com

となる.つまり個々のアドレスに対して別のサーブレットを割り当てることができるわけだが,普通は一つのサーブレットで処理することになるだろう.

メイル受信に必要な行程は以下

appengine-web.xml で宣言

下記をappengine-web.xmlに追記する.このへんは,XMPPの受信と同じ.

<inbound-services>
  <service>mail</service>
</inbound-services>

受信サーブレットのマップ

受信サーブレットをMailHandlerServletという名前だとして,web.xmlにこんな風に書く.

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

これだけだと,/_ah/mail/* に通常のHTTPで外部から接続されてしまうので,それを防ぐために下記のように追加.

<security-constraint>
  <web-resource-collection>
    <url-pattern>/_ah/mail/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>admin</role-name>
  </auth-constraint>
</security-constraint>

サーブレットを書く

サーブレットsmtp2webを使う場合とほとんど同じ.違いは,smtp2webでは,fromとtoをattributeとして取り出してくれたのだけど,appengineはそれをしてくれないというぐらい.サーブレットはこんな感じ.

@SuppressWarnings("serial")
public class MailHandlerServlet extends HttpServlet {
  public void doPost(HttpServletRequest req, 
      HttpServletResponse res)
     throws IOException {
    Properties prop = System.getProperties();
    Session session = Session.getInstance(prop, null);
    MimeMessage msg;
    try {
      msg = new MimeMessage(session, req.getInputStream());
      System.err.println("subject: " + msg.getSubject());
      
      for (Address addr: msg.getFrom())
        System.err.println("from:" + addr);
      for (Address addr: msg.getAllRecipients())
        System.err.println("to:  " + addr);      
      System.err.println("body: ---->" ); 
      System.err.print  (getText(msg)); 
      System.err.println("body: <----" ); 
    } catch (MessagingException e) {
      e.printStackTrace();
      throw new IOException("Message Excetion: " + e.getMessage());      
    }
  }
  private String getText(MimeMessage msg) 
  throws IOException, MessagingException{
    InputStream is = (InputStream)msg.getContent();
    StringBuffer sb = new StringBuffer();
    LineNumberReader lnr = 
      new LineNumberReader(new InputStreamReader(is, "ISO2022JP"));
    String s = null;
    while ((s = lnr.readLine()) != null)
      sb.append(s + "\n");
    return sb.toString();
  }
}

所感

App Engine 最大の死角が埋まり,とりあえず完成を見たという感じか.今後しばらくは完成度を高める方向に行くのだろう.つぎはRubyサポートかな?JRuby使えばいいからいらない?

追記 2009/12/11 どうも挙動が変わっている.

MimeMessage.getContent()で,以前はInputStreamが返ってきていたのに,今はStringが直接返ってきているようだ.どこのレイヤの変更かはわからない.

private String getText(MimeMessage msg) 
  throws MessagingException{
    return (String)msg.getContent();
}

でとりあえず動くようだ.が,本当はgetContent()で返ってくる型をちゃんと見ないといけないよな.反省.