メールでデータベースを更新するココロミ ~C++での即興実装

CodeZine / 2012年8月27日 14時0分

 僕のオシゴトは開発屋さんの後方支援に近いためか、試作の依頼が飛び込んでくることがよくあります。大まかな設計が固まったところで「ホントに動くの?」を確認したいってことですね。流用/改造はともかくも、新規プロジェクトでいきなり詳細/実装にとりかかるのはあまりにリスキー、キモとなる部分は試作品をこしらえて要求を満たすか確認/検証しておきたい、あるいは重大な問題を抱えているなら可及的速やかに軌道修正したい。加えて捨てるの覚悟の試作品に大きなコストはかけたくない(=手早くちゃっちゃと作りたい)。今回のお題はそんなおはなし。Boost,Xerces,ICU,SQLiteを使って、メールで送られてきたXMLからデータを抽出し、データベースに格納します。

■今回のお題

 先日引き受けた相談事は「メールを使ってデータベースにレコードを登録したい」とのこと。レコードを本文としたメールを投げて、そいつがデータベースに格納されるカラクリが欲しいってんですね。例えば売上データやらアンケート結果やら、あちこちで収集したデータをメールでかき集めるのが目的なのでしょう。「そんなもん、どこぞにWeb-serviceをホストしておいてHTTPで投げればいぃんじゃね?」と答えたところ、できるだけチープに実現したく、サーバ立てるのは避けたいそうな。

 なるほど、そう言うことなら納得です。テキトーなメールアカウントを1つ用意し、みんなが寄ってたかってそいつめがけてメールを投げつけ、小さなアプリケーションがメールを読んでデータベースに流し込むってスンポーですか。「登録したいレコードって定型なの? どれも同じフォーマットなら実装楽なんだけど」と訊くと依頼主曰く「いや、ゆくゆくはいろんな目的に利用することになりそうなので、フォーマットはできるだけ潰しの利くものにしてほしい」と。「んー…それじゃXMLでやっちゃいましょうか」ってことになり、「POP3からメールを取り出し、そこに書かれたXMLを解釈してデータベースに流し込む」小さなお試しアプリケーションを作ることになりました。

 「で、〆切はいつ?」「できれば明日までに」うひー…

■POP3からメールを取り出す

 まずはPOP3サーバーからメールを取り出すところから始めます。デキトーなメールアカウントに以下のようなメールを投げておきます。



 しばらく待って届いた頃を見計らってtelnetでPOP3に繋いで読み込んでみます。

telnetのログ
+OK Qpopper (version 4.0.9) at localhost starting. <9999.1345119383@localhost> USER episteme ■ +OK Password required for episteme. PASS abracadaburachichinnpuipui ■ +OK episteme has 3 visible messages (0 hidden) in 6481 octets. LIST ■ +OK 3 visible messages (6481 octets) 1 1451 2 2436 3 2594 . RETR 3 ■ +OK 2594 octets Return-Path: <episteme@cppll.jp> ...省略... X-UIDL: Do6"!I)=!!+0A"!0`P"! <?xml version='1.0' encoding='iso-2022-jp' ?> <words> <word en='dog' ja='B$$$LB' /> <word en='cat' ja='B$M$3B' /> </words> . DELE 3 ■ +OK Message 3 has been deleted. QUIT ■ +OK Pop server at localhost signing off.
 "■"のついた行は僕が入力した部分です。POP3に繋がったら:

USER <ユーザ名>とPASS <パスワード>を送信 LISTで得られた各メールIDに対し RETR <メールID>でナカミを取り出し 必要ならDELE <メールID>で削除して QUITで抜ける  この手順を生socketでちまちま書いてもさほどの手間はかからんけども、いかんせん〆切が迫っているのでBoost.Asioで済ませます。必要なのはXMLだけですから、RETRで得られる文字列のうち"<?xml..."から"."直前までを切り出します。

get_xml_from_pop3.cpp
#include <iostream> #include <sstream> #include <string> #include <vector> #include <iterator> #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif // Boost #include <boost/asio.hpp> using namespace std; using namespace boost::asio; /* * モロモロの定数値 */ // POP3 サーバのアドレスとポート const string pop3_address = "mail.dokkanoserver.com"; const string pop3_port = "110"; // POP3 アカウントとパスワード const string mail_user = "episteme"; const string mail_pass = "abracadabra"; inline bool starts_with(const string& str, const string& pattern) { return str.find(pattern) == 0; } template<typename OutputIterator> OutputIterator get_xml_from_pop3(const string& address, const string& port, const string& user, const string& pass, OutputIterator out) { string line; io_service io; ip::tcp::iostream pop3(address,port); vector<int> mail_id; getline(pop3, line); // USER pop3 << "USER " << user << endl; getline(pop3, line); if ( !starts_with(line,"+OK") ) goto quit; // PASS pop3 << "PASS " << pass << endl; getline(pop3, line); if ( !starts_with(line,"+OK") ) goto quit; // LIST pop3 << "LIST" << endl; getline(pop3,line); if ( !starts_with(line,"+OK") ) goto quit; // メールIDを読み取る for (;;) { getline(pop3,line); if ( !line.empty() && line[0] == '.' ) break; istringstream stream(line); int id; stream >> id; mail_id.push_back(id); } // RETR <メールID> でメールを読む for ( vector<int>::size_type i = 0; i < mail_id.size(); ++i ) { pop3 << "RETR " << mail_id[i] << endl; bool has_xml = false; ostringstream stream; // "<?xml..." から "."までを抽出 for (;;) { getline(pop3, line); if ( !line.empty() && line[0] == '.' ) break; if ( line.find("<?xml") == 0 ) { has_xml = true; } if ( has_xml ) stream << line << endl; } if ( has_xml ) { *out++ = stream.str(); // DELE <メールID> で削除 pop3 << "DELE " << mail_id[i] << endl; getline(pop3,line); } } quit: // QUIT pop3 << "QUIT" << endl; return out; } int main() { vector<string> msgs; get_xml_from_pop3(pop3_address, pop3_port, mail_user, mail_pass, back_inserter(msgs)); wcout << L"----------- I've got these XMLs ------------------" << endl; for_each(begin(msgs), end(msgs), [](const string& body) { cout << body << endl; }); }


■関連記事
メールでデータベースを更新するココロミ ~C++での即興実装
Google製のC++ Unit Test Framework「Google Test」を使ってみる
C++11 : スレッド・ライブラリひとめぐり
C++Builder XE2+FireMonkeyで昔のラケットゲームを再構築してみる(3)
開発ツールベンダーの老舗が語る C++によるソフトウェア開発の今と将来性

■記事全文へ

CodeZine

トピックスRSS

ランキング