サーバー・サイドの単純な 2G 携帯電話アプリケーション

簡単なサーバー・サイド・スクリプトを使用して、全世界の何十億台もの携帯電話で動作するアプリケーションを作成する

Comments

最近の技術関連ニュースは、iPhone、Droid、Palm Pre™ などの最新アプリケーションの話題があふれています。しかし、より幅広い話題を取り上げるニュース・メディアでは、もっと単純な携帯電話によって世界中に新たなコミュニケーションの機会や起業の機会が提供されており、その多くは電気も供給されない場所であることを報じています。そうしたニュースが示しているのは、それらの地域の多くではインターネットを利用したこともない人々が、携帯電話を会話のためだけに使うのではなく、例えば SMS テキスト・メッセージで情報を交換し、自らが行う (多くの場合は小さな) ビジネスの運営に役立てています。

私達の周りで、Web サイトの閲覧や多種多様な専用アプリケーションのインストールが可能な、高解像度カラー画面のタッチスクリーン付き携帯電話を使っていない友人や家族はどれくらいいるのでしょう。そうした人達が使っているのは、2G、つまり第 2 世代の携帯電話です。これらの携帯電話が 1990年代初めに登場した頃は、デジタル化され、テキスト・メッセージを送信できるという点で、第 2 世代の携帯電話は第 1 世代とは異なっていました。予算に限りがある人達にとって、2G 携帯電話を利用した場合の月額料金は、3G の場合に比べてはるかに安くて助かります。また世界の大部分の人々にとって、3G はまだ選択肢にすら入りません。昨年、Apple は 2500 万台の iPhone を販売しました。この数は非常に多いようにも思えますが、ITU (International Telecommunication Union: 国際電気通信連合) の最近の予測では、2010年末までに世界の 68 億人のうち携帯電話の契約者数は 50 億人にものぼると言われています。このことから、ここ数年の間は世界で使用されている 3G 携帯電話の台数はまだまだ少ないことがわかります。

2G 携帯電話は E メール・アドレスにテキスト・メッセージを送信することができます。また E メール・メッセージの内容に基づいて自動応答するスクリプトの作成は難しくありません。特に、そのスクリプトによって応答する対象となるメッセージが必ず 160 文字以下であることを考えれば、作成は容易です。そうしたことを考え合わせると、(大部分の 2G 携帯電話所有者にとって) リクエストを処理する専用の情報ソースのように見えるアプリケーションを作成できることがわかります。開発者としての皆さんは、皆さんが作成する関数に引数を渡す簡単な端末として携帯電話を考えれば、シンプルで安価な携帯電話の所有者に情報サービスを提供することがいかに簡単か、理解できるはずです。

一例として、米国の市外局番 3 桁で構成されるテキスト・メッセージを受け付け、その市外局番に関する情報を返すサービスを考えてみましょう。このサービスの使い方としては、例えば私の携帯電話に「不在着信」が表示されており、市外局番が 407 の誰かが私に電話をかけたことがわかるとします。私は市外局番 407 の場所はどこなのかと思い、私の 2G 携帯電話から Area Code Information (市外局番情報) サービスに対し、SMS テキスト・メッセージで「407」を送信します。するとそのサービスから、Florida (Orlando, Florida, St. Cloud and central eastern Florida) というテキスト・メッセージが返送されます。(この記事では、このサービスの E メール・アドレスを acinfo@snee.com としていますが、(皆さん自身で試せる) 実際のアプリケーションでは E メール・アドレスは「acinfo」ではなく「aci」です。)

このアプリケーションの基本的なステップを以下に示します。どのステップも単純なスクリプトを使用しています。

  1. 受信したすべての E メールをチェックします。acinfo@snee.com から何かを受信した場合には、そのメールを aci.py という Python スクリプトに渡し、この aci.py スクリプトが残りのステップを実行します。
  2. 市外局番情報のリストの中で、受信した E メール本体の中にあるテキストと一致する番号を検索します。
  3. その番号がリストの中にある場合には、その市外局番に関して保存されている情報を含むように返信メッセージを設定します (上の例では、Florida (Orlando, Florida, St. Cloud and central eastern Florida) と設定します)。
  4. その番号がリストの中にない場合には、その受信メッセージに関する情報が見つからなかったことを示す返信メッセージを設定します。
  5. 最初のメッセージを発信したアドレスに返信メッセージを送信し、そのメッセージをログに記録します。

私のアプリケーションでは、単純なテキスト・ファイルの中を検索することで情報の検索を行いました。しかし皆さんのアプリケーションの場合には、皆さんの想像力と、スクリプトから利用できるデータ・ソース次第で、いくらでも複雑なことをすることができます。

受信した E メールをチェックし、適切な処理プログラム (procmail) に渡す

受信メッセージへの応答を自動化するための鍵は、procmail という古き良き UNIX® ユーティリティーにあります。初期の E メール・システムの多くは、スパムのスキャンや特定フォルダーへの自動ソートを E メールのヘッダー情報をベースに行っており、そのために procmail が使われていました。そして今でも、そうした用途に procmail を使用することができます。皆さんが利用しているアカウントのホスト・プロバイダーが Linux® ベースのシステムを使用しており、シェルへのアクセスを許可している場合には、そのアカウント用の procmail 構成ファイルを作成できるはずであり、その構成ファイルによって受信 E メールのパターンをスキャンし、検出された内容に基づいてアクションを実行できるはずです。

この構成ファイル (.procmailrc と呼ばれます) に E メールを渡すためには、1 つか 2 つの追加ステップが必要かもしれません。かつては .forward ファイルを作成し、このファイルによって E メールを .procmailrc に渡していました。しかし現在ではホスト・プロバイダーが Web フォームを用意していて、そのフォームに入力することで、E メールを受信したら .procmailrc ファイルをチェックするようにシステムに指示できるようになっています。

そのように私のホスト・プロバイダーでアカウントを構成した後、私は以下の 3 行のルールを .procmailrc ファイルに追加しました。

:0
* ^To: <?acinfo@snee.com>?
| /usr/home/bobd/aci/aci.py

最初の行は procmail による処理の開始を示しています。2 行目はアスタリスクで始まり、これから条件を指定することが示されています。この行には続いて正規表現があり、この正規表現によって E メールの中の検索対象を指定します。ここでの検索対象は行の先頭にある「To: acinfo@snee.com」であり、E メール・アドレスの前後には不等号括弧が付く場合があります (不等号括弧がある場合もない場合もあることからわかるように、多種多様な E メール・クライアントや携帯電話からの E メールを処理する際には、必ずしもフォーマットに一貫性がないことへの対応を考えておく必要があります)。ここで作成した E メール・アドレスは市外局番情報のリクエスト以外には使用しないため、このアドレスに送信されるメッセージにはすべて、このルールが適用されます。

.procmailrc ルールの 3 行目はメッセージの転送先となるメールボックスの名前を指定します。しかしここでは、もっと面白いことをしています。このパイプ記号は、メッセージの内容を指定のプログラム (aci.py という Python スクリプト) の入力として送るように、と指示しています。

入力を解析し、スクリプト言語を選択する

aci.py プログラムを見る前に、このプログラムで処理する入力を見てみましょう。SMS テキスト・メッセージが E メールとして表示され、また送信者のアドレスが、電話番号と (電話会社が使用する) ドメイン名で表現されて表示されます。リスト 1 は SMS による E メールの表示例を示しています。この場合は LG の携帯電話 env2™ から、Verizon のネットワークを使ってテキスト・メッセージとして市外局番 407 を送信しています。ただし発信元の電話番号はリスト 1 では (434) 000-0000 に変更してあります。

リスト 1. E メール版の SMS テキスト・メッセージ
From 4340000000@vtext.com Wed Mar 10 00:50:01 2010
Return-Path: <4340000000@vtext.com>
Delivered-To: bobd-snee:com-acinfo@snee.com
X-Envelope-To: acinfo@snee.com
Received: (qmail 21729 invoked from network); 10 Mar 2010 00:50:00 -0000
Received: from mailwash38.pair.com (66.39.2.38)
         by oomur.pair.com with SMTP; 10 Mar 2010 00:50:00 -0000
Received: from localhost (localhost [127.0.0.1])
         by mailwash38.pair.com (Postfix) with SMTP id 021054142C
         for <acinfo@snee.com>; Tue,  9 Mar 2010 19:50:00 -0500 (EST)
X-Spam-Check-By: mailwash38.pair.com
X-Spam-Status: No, hits=2.9 required=4.0 tests=BAYES_00, FROM_STARTS_WITH_NUMS,
         MISSING_SUBJECT, TVD_SPACE_RATIO autolearn=no version=3.002005
X-Spam-Flag: NO
X-Spam-Level: **
X-Spam-Filtered: a7b240700a36d5e6c2608f9ce43a92c9
Received: from lrx5634xmtasa.alltel.net (lrx5634xmtasa.alltel.net
         [205.142.19.193])
         by mailwash38.pair.com (Postfix) with ESMTP id 3FF774142F
         for <acinfo@snee.com>; Tue,  9 Mar 2010 19:49:59 -0500 (EST)
X-Policy: RELAYLIST-$RELAYED
Received: from unknown (HELO ifs2006qwigfe) ([10.135.9.57])
         by lrx5634xmtasa.alltel.net with ESMTP; 09 Mar 2010 18:49:58 -0600
Message-ID: <26597005.1268182198841.JavaMail.root@ifs2006qwigfe>
From: 4340000000@vtext.com
To: acinfo@snee.com
Subject: 
Mime-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Date: Tue,  9 Mar 2010 19:49:59 -0500 (EST)

407

ここには大量の内容が含まれていますが、スクリプトに必要な情報は、このメッセージを送信した機器の E メール・アドレス (4340000000@vtext.com) と、その機器が送信したメッセージ (最後の行の 407) の 2 つだけです。単純なテキスト処理スクリプトを作成する場合の言語として、私は通常、まず Perl を使います。そして Perl スクリプトの作成に手間はかかりませんでした。必要なことは、リスト 1 の E メールから E メール・アドレスとメッセージを抽出し、そのメッセージを調べて市外局番リストと突き合わせ、要求された情報を送信元の携帯電話の E メール・アドレスに返送するだけです。

この Perl スクリプトは私の携帯電話からのメッセージには適切に動作しましたが、他の携帯電話でテストしてみると、携帯電話から送信される E メール・メッセージには思ったほど一貫性がないことがわかりました。先ほど触れたように、.procmailrc ファイルは、不等号括弧に囲まれた E メール・アドレスにも囲まれていない E メール・アドレスにも対応する必要があります。これは Perl で簡単に処理することができます。しかしよく調べてみると、E メール・メッセージの構造には他にも異なる部分があり、それらに対応が必要なことがわかりました。

リスト 2 はもっと複雑なメッセージを示しています。この場合は iPhone から送信されたマルチパート MIME メッセージとして「305」というメッセージを受信しています (iPhone は確かに 2G 携帯電話ではありませんが、私は iPhone を使ったテストもする価値があると考えたのです)。このリストの中で「305」を懸命に探す必要はありません。この「305」はエンコードされているからです。メッセージの中でデコード対象となる部分を見つけ、それからデコード処理をしようとすると、Perl スクリプトは次第に長くなり、さらに他の数機種の携帯電話に対応させると、膨大なものになってしまいました。

リスト 2. さらに複雑な、E メール版の SMS メッセージ
From 6170000000@mms.att.net Sun Feb 28 21:00:03 2010
Return-Path: <6170000000@mms.att.net>
Delivered-To: bobd-snee:com-acinfo@snee.com
X-Envelope-To: acinfo@snee.com
Received: (qmail 18219 invoked from network); 28 Feb 2010 21:00:03 -0000
Received: from mailwash38.pair.com (66.39.2.38)
         by oomur.pair.com with SMTP; 28 Feb 2010 21:00:03 -0000
Received: from localhost (localhost [127.0.0.1])
         by mailwash38.pair.com (Postfix) with SMTP id B1D8A41430
         for <acinfo@snee.com>; Sun, 28 Feb 2010 16:00:02 -0500 (EST)
X-Spam-Check-By: mailwash38.pair.com
X-Spam-Status: No, hits=3.0 required=4.0 tests=BAYES_20, FROM_STARTS_WITH_NUMS,
         TVD_SPACE_RATIO autolearn=no version=3.002005
X-Spam-Flag: NO
X-Spam-Level: ***
X-Spam-Filtered: a7b240700a36d5e6c2608f9ce43a92c9
Received: from schemailmta08.cingularme.com (schemailmta08.cingularme.com
         [209.183.37.70])
         by mailwash38.pair.com (Postfix) with ESMTP id F35394142C
         for <acinfo@snee.com>; Sun, 28 Feb 2010 16:00:00 -0500 (EST)
X-Mms-MMS-Version: 18
Date: Sun, 28 Feb 2010 15:13:10 -0600
X-Nokia-Ag-Internal: ; smiltype=false; internaldate=1267391590642
Content-Type: multipart/mixed;
         boundary="----=_Part_9705244_14454315.1267391590647"
Received: from schagw01 ([172.16.130.170]) by schemailmta08.cingularme.com
         (InterMail vM.6.01.04.00 201-2131-118-20041027) with ESMTP id
         <20100228210001.QHEZ5910.schemailmta08.cingularme.com@schagw01>
         for <acinfo@snee.com>; Sun, 28 Feb 2010 15:00:01 -0600
X-Mms-Transaction-ID: 1267390700-6
From: <6170000000@mms.att.net>
To: acinfo@snee.com
Mime-Version: 1.0
Message-ID: <33144584.1267391590647.JavaMail.wluser@schagw01>
X-Mms-Message-Type: 0
Subject: Multimedia message
X-Nokia-Ag-Version: 2.0

------=_Part_9705244_14454315.1267391590647
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: base64
Content-Disposition: inline

MzA1
------=_Part_9705244_14454315.1267391590647--

この時点で、私はプログラミング言語を選択する際の重要な判断基準を思い出しました。その基準とは、アプリケーションのごくありふれたタスクを処理するために、そのプログラミング言語でどんなライブラリーを利用できるかというものです。E メール・ヘッダーを解析してメッセージ部分を発見し、必要に応じてメッセージをデコードする、という処理がごくありふれたタスクであることは明らかです。私は CPAN で 1 つの Perl モジュールを見つけました。そのモジュールは、さまざまに異なるヘッダー・フォーマットの E メールが存在する可能性があるなかで、関数を呼び出すことで各種の E メール・メッセージ・ヘッダーを解析し、必要な情報を取得することができます。しかしそのライブラリーは他の Perl ライブラリーに依存しており、私が利用しているホスト・プロバイダーが提供しているのは、そうしたライブラリーの古いバージョンのみでした。そこで私は Python で似たものがないかを調べてみました。そして、Python の email パッケージを見つけ、いくつかの簡単なテストを作成し、プログラムを Python で作り直すことに決めました (この email パッケージへのリンクは「参考文献」を参照してください)。少し Web を検索してみると、Ruby、Java™、PHP、その他のプログラミング言語にも同じようなライブラリーがあることがわかりました。つまり E メールに自動応答するスクリプトを作成する場合には、Perl と Python のみに限定されることはありません。

自動応答スクリプト

リスト 3 は aci.py スクリプトを示しています。先頭の import 文によって、email.Parser ライブラリーがインポートされ、また他のいくつかの一般的な Python ライブラリーもインポートされていることに注意してください。一番下の __main__ セクションには、このプログラムの基本的なロジックが含まれています。つまり受信したメッセージを解析し、そのメッセージからメッセージ送信者の E メール・アドレスと (areaCode 変数の中に保存された) メッセージ本体を取得し、そこに含まれる特定の市外局番に関する情報を (このスクリプトの他の場所で定義されている areaCodeInfo 関数を使って) 検索し、その情報を応答として送信し、そのメッセージをログに記録しています。

リスト 3. aci.py script
#!/usr/local/bin/python

# aci.pl: (area code information) read e-mail message to find area
# code, then send information about that area code.
# Bob DuCharme 2010-01 no warranty expressed or implied

import os
import email.Parser
import sys
import datetime
import re

def multipartBody(msg):
# following code from 
# http://docs.python.org/library/email-examples.html

  partCounter=1

  for part in msg.walk():
    if part.get_content_maintype()=="multipart":
      continue
    name=part.get_param("name")
    if name==None:
      name="part-%i" % partCounter
    partCounter+=1
    msgText = part.get_payload(decode=1)

  msgSender = msgSenderText
  return msgText.strip()      # strip whitespace


def areaCodeInfo(areaCode):

  # Look for data about that area code in areacodes.txt.
  # First initialize values that should get overridden.

  response = "No information available for area code " + areaCode + "."
  foundAreaCode = False
  line = "dummy"
  acFile = open(aciPath + "areacodes.txt")
  while ((not foundAreaCode) and line):
    line = acFile.readline()
    if (line[0:5] == areaCode + ": "):   # e.g. "212: " 
      response = line
      foundAreaCode = True

  return response


def sendReply(msgSender,response):

  f = os.popen("%s -t" % SENDMAIL, "w")
  f.write("To: " + msgSender + "\n")
  f.write("From: area code information <acinfo@snee.com>\n")
  f.write("Return-Path: area code information <acinfo@snee.com>\n")
  f.write("Content-type: text/plain\n\n")
  f.write(response)
  sts = f.close()

def  logIt(msgSenderText,areaCode):

  timestamp = datetime.datetime.today().isoformat()[0:19] 
  log = open(aciPath + "log.txt",'a')
  log.write(timestamp + " " + msgSenderText + " " + areaCode + "\n") 
  log.close()


if __name__ == "__main__":

  SENDMAIL = "/usr/sbin/sendmail" # sendmail location
  aciPath = "/usr/home/bobd/aci/"
  keepFullLog = False # For debugging. More detailed than log.txt.
  response = ""

  # Parse the standard input to find the message and sender value
  mailFile=sys.stdin
  p=email.Parser.Parser()
  msg=p.parse(mailFile)
  mailFile.close()

  msgSenderText = msg['From']  # text showing msg sender's name
  msgSender = msgSenderText    # save msgSenderText for log

  # If msgSender has the form "Some Guy <someguy@example.com>" 
  # then we just want someguy@example.com
  emailAddrRegEx = re.compile(r"\<(?P<emailAddr>.+)\>")
  result = emailAddrRegEx.search(msgSender)
  if result != None:
      msgSender = result.group('emailAddr') 

  if keepFullLog:
    output = open(aciPath + "fulllog.txt",'a')
    output.write(str(msg) + "\n-- end of mail msg --\n\n")
    output.close()

  if msg.has_key("X-Mailer") and \
        msg["X-Mailer"][0:24] == "Microsoft Office Outlook":
    response = "Microsoft Outlook format is not supported."
    areaCode = ""
  else:
    areaCode = multipartBody(msg)
    response = areaCodeInfo(areaCode)

  sendReply(msgSender,response)
  logIt(msgSenderText,areaCode)

いくつか注意点を挙げておきます。

  • logIt 関数は log.txt というテキスト・ファイルに 1 行のデータを保存します。この行には、ログに記録された時刻、情報を要求したモバイル機器の E メール・アドレス、その機器がどの市外局番に関する情報を要求したのか、が記録されます。この行は、例えば 2010-03-19T19:50:02 4340000000@vtext.com 407 のようになります。keepFullLog というブール変数が True に設定されると、このプログラムは受信したメッセージ全体を別のログ・ファイルに保存します。こうすると非常に冗長になりますが、デバッグには非常に貴重です。
  • Microsoft® Outlook® によって送信される E メール・メッセージのフォーマットを email.Parser ライブラリーで処理することはできませんでした。しかし Microsoft Outlook で送信する 2G 携帯電話はないはずです。Outlook メッセージを検出すると、このスクリプトは適切な応答を送信します。
  • このスクリプトは E メール・メッセージを送信するために、sendmail という非常に古い UNIX ユーティリティーを使っています。sendmail は呼び出して使える関数をいくつも持つような特別なライブラリーではありませんが、UNIX の基本設計思想に基づく動作をし、あるコンポーネントから別のコンポーネントへと情報をパイプすることでアプリケーションのコンポーネントを組み立てることができます。この Python スクリプトはその哲学に従って sendmail を扱います。それは、あたかもテキスト・ファイルであるかのように sendmail を開き、適切な情報を sendmail に書き込んで、それから sendmail を「閉じて」います。
  • areaCodeInfo 関数は areacodes.txt というファイルの中で情報を検索します。このファイルから抜粋したものをリスト 4 に示します。このファイルはウィキペディアをベースにしています。

リスト 4. areacodes.txt からの抜粋
210: Texas (San Antonio area)
211: Community Services Hotline (e.g., crisis line, United Way, etc.)
212: New York (Manhattan except for Marble Hill)
213: California (central Los Angeles)
214: Texas (Dallas area)

テキスト・ファイルを検索してストリングを見つけ、そのストリングが見つかった行を返すアプリケーションは、非常に単純なアプリケーションです。2G 携帯電話からのメッセージに応答する独自のアプリケーションを作成する場合、より創造的なものにするために、次のような例が考えられます。例えばローカル・ストアとリモート・ストアの任意の組み合わせに対してデータベース・クエリーを実行し、その結果見つかる情報に対して相互参照を行い、多種多様なロジックを実行した後、そのクエリーの送信元の携帯電話にとって有用な情報を返す、というようなプログラムを作成することができます。SMS メッセージの制限である 160 文字を超えない限り、そうしたことが可能です。

アプリケーションのコマンドライン・インターフェースを実行するクライアントとして、携帯電話を考えてみてください。そのアプリケーションは単純なスクリプトで構わず、そのスクリプトは困難で複雑な作業を他のライブラリーに任せるだけでよい、ということを忘れないでください。世界中で何十億台も使われている携帯電話用のサーバー・サイド・アプリケーションの開発は、驚くほど簡単なのです。


ダウンロード可能なリソース


関連トピック

  • ヒント: XMLを使ってSMSメッセージを送信する」(Nicholas Chase 著、developerWorks、2004年6月) は、SMS メッセージがどのように送信されるかの背景を、アプリケーションに Web サービスを統合する実際の例を使って解説しています。
  • SMS: Case study of a Web services deployment」(Cameron Laird 著、developerWorks、2001年8月) を読んでください。かなり以前の記事ですが、Web サービス・アーキテクチャーでの SMS メッセージの役割の可能性について、興味深い視点から解説しています。
  • Wikipedia で List of North American Numbering Plan area codes の項目を見てください。このリストには、米国、カナダ、カリブ海地域、米国太平洋地域の市外局番が網羅されています。
  • Common Short Code Administration の Web サイトを調べ、ホワイトペーパー、FAQ、その他をとおして Common Short Code の利用方法を学んでください。
  • procmail のホームページには、procmail の簡単な使い方や高度な使い方を説明した多くの優れたリソースへのリンクが用意されています。
  • My developerWorks で developerWorks のエクスペリエンスをパーソナライズしてください。
  • XML および関連技術において IBM 認定技術者になる方法については、IBM XML certification を参照してください。
  • developerWorks に参加し、Twitter で developerWorks をフォローしてください。
  • developerWorks podcasts ではソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。
  • E メール・メッセージやヘッダー解析する Perl ライブラリー、Email::Simple を CPAN から入手しください。
  • E メール・メッセージやヘッダー解析するパッケージ、Python email ライブラリーをダウンロードしてください。
  • IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2®、Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Open source, Web development
ArticleID=497419
ArticleTitle=サーバー・サイドの単純な 2G 携帯電話アプリケーション
publish-date=05252010