Простые приложения на стороне сервера для телефонов стандарта 2G

Как создать приложение, работающее с миллионами телефонов по всему миру, написав небольшой серверный сценарий

Comments

Ленты технических новостей полны историй о новейших приложениях для iPhone, Droid и Palm Pre™™. Однако более внимательные аналитики отмечают, что даже самые простые мобильные телефоны открывают новые возможности для взаимодействия и предпринимательства по всему миру, часто в тех местах, где нет постоянных источников электропитания. Для многих людей, живущих в подобных местах, это единственный способ доступа к Интернету, и они используют свои мобильные телефоны не только для разговоров, но и для обмена информацией посредством SMS-сообщений, позволяющего им поддерживать свой собственный (часто микроскопический) бизнес.

Оглянитесь вокруг - сколько ваших друзей и близких имеют телефоны с большим цветным сенсорным экраном высокого разрешения, с возможностями просмотра Web-страниц и установки широкого набора приложений? Те, у кого нет таких аппаратов, пользуются обычными телефонами второго поколения (стандарт 2G). Когда такие телефоны появились в начале 1990-х годов, их отличия от аппаратов первого поколения заключались в том, что они стали цифровыми и научились передавать текстовые сообщения. С финансовой точки зрения телефоны второго поколения значительно экономнее; а сети 3G доступны лишь малой части абонентов во всем мире. За последний год компания Apple продала 25 миллионов устройств iPhone. Цифры впечатляющие, однако, если посмотреть на последние оценки Международного союза электросвязи, согласно которым к концу 2010 года услугами мобильной связи будут пользоваться 5 из 6,8 миллиардов жителей Земли, можно прийти к выводу, что в ближайшие несколько лет относительная доля используемых 3G-телефонов будет не так уж велика.

Телефоны второго поколения умеют отправлять текстовые сообщения на адреса электронной почты, и совсем несложно написать сценарий, который будет автоматически отвечать на входящее электронное сообщение, формируя ответ на основе его содержимого, особенно, если вы уверены, что входящее сообщение не будет превышать 160 символов. Таким образом можно создавать приложения, которые с точки зрения большинства пользователей мобильных телефонов 2-го поколения будут выглядеть просто как специализированные источники информации, обрабатывающие их запросы. Если с позиции разработчика ПО рассматривать сотовые телефоны как небольшие терминалы, передающие аргументы в написанные вами функции, вы увидите, как просто предоставлять информационные услуги владельцам простых недорогих телефонов.

В качестве примера давайте рассмотрим службу, которая принимает текстовое сообщение, состоящее из трех цифр телефонного кода региона США, и возвращает информацию об этом регионе. Предположим, например, что в моем мобильном телефоне осталась запись о пропущенном звонке – кто-то звонил мне из города с кодом 407. Если я хочу узнать, что это за область, я посылаю службе информирования о региональных кодах SMS-сообщение с текстом "407" со своего 2G-телефона и получаю в ответ сообщение: Florida (Orlando, Florida, St. Cloud and central eastern Florida). В этом руководстве для примера используется электронный адрес службы acinfo@snee.com, но в реальном работающем приложении, которое вы можете испытать самостоятельно, вместо имени "acinfo" следует использовать имя "aci".

Базовый алгоритм работы приложения, реализуемый в виде простого сценария, выглядит следующим образом:

  1. Проверка входящей электронной почты. Когда на адрес acinfo@snee.com приходит сообщение, оно перенаправляется сценарию Python aci.py, который выполняет остальные действия.
  2. Выполняется проверка списка кодов областей и поиск в нем текста, содержащегося в теле полученного письма.
  3. При нахождении полученного текста формируется ответное сообщение. В рассмотренном примере это сообщение Florida (Orlando, Florida, St. Cloud and central eastern Florida).
  4. Если совпадение не найдено, формируется ответное сообщение о том, что запрошенная информация не найдена.
  5. Отправка ответного сообщения на адрес отправителя с занесением в журнал.

В моем приложении поиск информации осуществляется путем просмотра обычного текстового файла, но вы можете использовать в вашем сценарии любые допустимые источники данных, которые вам подскажет ваше воображение.

Procmail: проверка и передача входящей почты программе обработки

Ключевым звеном в автоматизации ответов на входящие сообщения является старая добрая утилита UNIX® под названием procmail. На procmail были основаны многие из первых систем автоматической фильтрации спама и сортировки сообщений по определенным папкам в зависимости от содержимого заголовка, и эта утилита используется до сих пор. Если ваш хост-провайдер использует Linux®-систему и предоставил вам доступ к программной оболочке, вы сможете создать для вашей учетной записи конфигурацию procmail, которая будет сканировать текстовые шаблоны во входящих сообщениях и в зависимости от их содержимого выполнять различные действия.

Чтобы организовать маршрутизацию электронной почты в соответствии с конфигурационным файлом, который называется .procmailrc, нужно выполнить пару дополнительных действий. Раньше для этого требовалось создать файл .forward, который обеспечивал перенаправление почтовых сообщений, но сегодня, как правило, все хост-провайдеры предоставляют в ваше распоряжение Web-интерфейс, в котором можно задать проверку поступающей электронной почты по файлу .procmailrc.

Я задал соответствующую настройку для моей учетной записи и затем добавил в файл .procmailrc правило, состоящее из трех строк:

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

Первая строка означает начало набора команд procmail. Вторая строка начинается со знака звездочки, означающего, что вы определяете условие. Оставшаяся часть строки содержит регулярное выражение, указывающее, какой фрагмент необходимо искать во входящем сообщении, а именно: подстроку "To: acinfo@snee.com", расположенную в начале строки, при этом адрес электронной почты может быть заключен в угловые скобки. Угловые скобки – это лишь один из примеров несовместимостей, которые необходимо учитывать при обработке почты, поскольку она может отправляться различными почтовыми клиентами и с различных телефонов. Этот созданный мною почтовый адрес предназначен исключительно для обработки запросов на получение кодов регионов, поэтому правило применяется ко всем сообщениям, приходящим на этот адрес.

Третья строка правила .procmailrc могла бы содержать имя почтового ящика, в который должны доставляться сообщения, но здесь мы создали более интересную конструкцию. Символ конвейера (|) означает, что содержимое сообщения должно перенаправляться на вход указанной программы - сценария Python с именем aci.py.

Разбор входных данных и выбор языка для написания сценария

Прежде чем перейти к рассмотрению программы aci.py, взгляните на входные данные, которые она должна обработать: это текстовое SMS-сообщение, представленное в виде электронного письма. Это письмо содержит адрес отправителя, сформированный на основе телефонного номера и доменного имени, используемого телефонной компанией. В листинге 1 приведен пример электронного письма, которое мы получаем в результате отправки текстового SMS-сообщения 407 с телефона LG env2™ из сети Verizon (настоящий номер телефона заменен в листинге на номер (434) 000-0000).

Листинг 1. 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

Этот листинг содержит много информации, но нашему сценарию нужны только два фрагмента: электронный адрес устройства, отправившего сообщение (4340000000@vtext.com), и непосредственно сам текст сообщения (407, в последней строке). Обычно для написания сценариев, выполняющих обработку простого текста, я использую в первую очередь язык Perl. На этом языке я легко написал сценарий, который "вытаскивает" электронный адрес и сообщение из полученного письма (листинг 1), ищет полученный текст в списке кодов областей и отсылает запрошенную информацию на электронный адрес, представляющий собой телефон отправителя.

Этот сценарий Perl отлично работал с сообщениями, отправляемыми с моего телефона, но при работе с другими моделями телефонов я обнаружил, что формат отправляемых ими электронных сообщений не так универсален, как мне бы хотелось. Ранее я уже обращал ваше внимание на то, что в файле .procmailrc должны учитываться адреса, которые могут быть (а могут и не быть) заключены в угловые скобки – это тоже несложно обработать с помощью Perl. С учетом всего этого я пришел к выводу, что оставшаяся структура электронного сообщения может содержать и другие отличия, которые следует принять во внимание.

В листинге 2 показано более сложное сообщение, отправленное с iPhone, которое доставляет текст "305" в виде составного сообщения в кодировке MIME (конечно, iPhone – это не 2G-аппарат, но я подумал, что было бы неплохо протестировать и его). Не пытайтесь отыскать здесь текст "305", поскольку он закодирован. Добавление в сценарий Perl функций поиска и декодирования нужной части сообщения привело к тому, что он становился все больше и больше, а с учетом имеющегося кода для поддержки других моделей телефонов он и так уже был немаленьким.

Листинг 2. 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--

В этот момент я вспомнил, что при выборе языка программирования важным критерием является наличие в нем библиотек, предназначенных для решения типовых задач, встречающихся в приложениях, а разбор заголовка электронного сообщения, поиск фрагмента, содержащего тело письма, и его декодирование – это определенно типовые задачи. В архиве CPAN я нашел модуль Perl, выполняющий разбор различных заголовков сообщений электронной почты независимо от их формата, с помощью которого я мог бы получать нужную информацию, просто вызывая определенные функции. Однако этот модуль оказался зависящим от других библиотек Perl, версии которых на ресурсах провайдера хостинга оказались устаревшими. Поэтому я попытался выяснить, что в этом направлении может предложить Python. Я нашел нужную библиотеку под названием email (ссылку для ее загрузки вы можете найти в разделе Ресурсы), написал несколько простых тестов и решил переписать свою программу на Python. Немного поискав в сети, вы легко найдете ссылки на аналогичные библиотеки для Ruby, Java™, PHP и других языков программирования, поэтому для написания сценария автоответчика совершенно не обязательно использовать Perl или Python – вы можете выбрать тот язык программирования, который захотите.

Сценарий автоответчика

В листинге 3 приведен код сценария aci.py; обратите внимание на то, что с помощью операторов import включается использование библиотеки email.Parser и нескольких других распространенных библиотек Python. В разделе __main__ содержится основная логика программы: разбор входящего электронного сообщения, определение электронного адреса отправителя и тела сообщения (которое сохраняется в переменной areaCode), поиск информации о полученном коде региона с помощью функции areaCodeInfo, определенной в другом месте программы, передача этой информации назад отправителю и занесение сообщения в журнал.

Листинг 3. Сценарий aci.py
#!/usr/local/bin/python

# aci.pl (информация о коде региона): чтение e-mail
# сообщения для определения кода запрашиваемого 
# региона и отправка найденной информации.
# Боб дю Шарм, январь 2010, никаких явных или неявных гарантий

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

def multipartBody(msg):
# следующий код взят из документа 
# 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()      # удаляем пробелы


def areaCodeInfo(areaCode):

  # Поиск данных о полученном коде области в файле areacodes.txt.
  # Сначала проинициализируем переменные, которые должны быть замещены.

  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 + ": "):   # например, "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
  aciPath = "/usr/home/bobd/aci/"
  keepFullLog = False # Требуется для отладки. Более детально, чем в файле log.txt.
  response = ""

 # Выполним разбор входных данных, чтобы найти отправителя и тело сообщения
  mailFile=sys.stdin
  p=email.Parser.Parser()
  msg=p.parse(mailFile)
  mailFile.close()

  msgSenderText = msg['From']  # текст, показывающий имя отправителя сообщения msg
  msgSender = msgSenderText    # сохранение значения msgSenderText для занесения в журнал


   # Если значением msgSender является текст типа
   # "Какой-то парень <someguy@example.com>", 
   # выбираем только нужную подстроку 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 одну строку. В ней содержатся дата события, e-mail адрес сотового аппарата, с которого была запрошена информация, и запрашиваемый код области. Эта строка выглядит следующим образом: 2010-03-19T19:50:02 4340000000@vtext.com 407. Если значение логической переменной keepFullLog установлено в True, программа сохраняет входящие сообщения целиком в отдельном log-файле. Несмотря на то, что при этом протоколируется много подробной информации, данная возможность оказывается очень полезной при отладке программы.
  • Библиотека email.Parser не умеет работать с электронными сообщениями, отправленными через Microsoft® Outlook®, однако сообщения в таком формате не отправляет ни один 2G-аппарат. При обнаружении сообщения в формате Outlook программа отправит абоненту соответствующий ответ.
  • Для отправки электронных сообщений сценарий использует другую известную утилиту UNIX – sendmail. Эта программа не является какой-то специальной библиотекой с вызываемыми функциями, а действует в соответствии с философией UNIX, заключающейся в передаче компонентов приложений от одного к другому с помощью конвейеров. Сценарий Python рассматривает утилиту 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-телефонов, вы можете подойти к делу более творчески. Ваша программа может обращаться к базам данных (как к локальным, так и к удаленным), использовать перекрестные ссылки и выполнять любые логические операции с данными, возвращая полученные результаты на телефон, с которого пришел запрос. Все, чем вы ограничены – это 160 символов SMS-сообщения.

Рассматривайте мобильный телефон как клиентское устройство, работающее с вашим приложением через интерфейс командной строки, и помните, что ваше приложение может быть простым сценарием, использующим для выполнения трудной и сложной работы различные библиотеки. Вы убедитесь, что разработка серверной части приложения, работающего с миллионами абонентов по всему миру, может оказаться на удивление простой.


Ресурсы для скачивания


Похожие темы

  • Оригинал статьи Simple server-side 2G phone apps (EN).
  • Статья Николаса Чейза (Nicholas Chase) Совет: используйте XML для отправки SMS-сообщений (EN) (developerWorks, июнь 2004 г.) описывает процессы передачи SMS-сообщений и содержит практические примеры интеграции Web-служб с приложениями.
  • Прочитайте статью Камерона Лэйрда (Cameron Laird) SMS: учебный пример по развертыванию Web-служб (EN) (developerWorks, август 2001 г.), в которой интересно описан ранний взгляд на возможную роль SMS-сообщений в архитектуре Web-сервисов.
  • Просмотрите в Википедии список List of North American Numbering Plan area codes (EN), содержащий телефонные коды областей Соединенных штатов, Канады, Карибских территорий и Тихоокеанского побережья США.
  • Загрузите из архива CPAN библиотеку Perl Email::Simple (EN), предназначенную для работы с заголовками и сообщениями электронной почты.
  • Загрузите библиотеку email для Python (EN), предназначенную для работы с заголовками и сообщениями электронной почты.
  • На Web-сайте CSCA (EN) вы узнаете о том, как работает эта организация, познакомитесь с официальными документами и ответами на часто задаваемые вопросы, а также найдете много другой полезной информации.
  • На домашней странице Procmail (EN) вы найдете ссылки на различные ресурсы, описывающие как простые, так и сложные сценарии использования этой утилиты.
  • Узнайте, как получить квалификацию сертифицированного разработчика компании IBM в области XML и связанных технологий, на сайте Сертификация IBM XML (EN).

Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML, Open source
ArticleID=833187
ArticleTitle=Простые приложения на стороне сервера для телефонов стандарта 2G
publish-date=09052012