まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:twitter

このブログでも紹介させていただいたのですが、最近「手書きイラストから『いらすとや』のイラストを検索するアプリ」を作りました:
手書きイラストで「いらすとや」のイラストを検索する




この時の技術を応用して、「ツイッターに手書きイラストを添付してつぶやく」ことができる「イラツイ」というアプリを作って、UI/UX とかまだ全然ダメダメですがとりあえず動くようになったのと、自分が使っていて面白かった(大事)ので公開してみました:
http://iratwi.mybluemix.net/


PC のブラウザからも(マウスやタッチパッドで)使えるつもりですが、一応スマホブラウザからの利用を想定して画面を最適化しています。PC やスマホのウェブブラウザで上記の URL (http://iratwi.mybluemix.net/) にアクセスすると、こんな↓画面になります。画面左上の "t" マークをクリックして、ご自身の Twitter ID でログインしてください:
IMG_0745


↓こんな感じの画面になったら(左上が自分の Twitter アイコンになったら)ログイン成功です。画面は「イラツイ」と「一覧」という2つのタブから構成されています。デフォルトの「イラツイ」でキャンバス(画面中央の白い部分)にマウスや指などでイラストを手書きしてください。線の色や太さはキャンバスの上から選択できます:
IMG_0746


例えば試しにこんな感じのを書いてみました。このイラストをツイートするには画面下部の "Tweet" をクリックします:
IMG_0742


するとツイートメッセージを入力する画面が表示されます。ここに適当にメッセージを入力して、「ツイート」ボタンで投稿します:
IMG_0743



「一覧」タブに切り替えると、自分のツイートした内容が新しい順に表示されます。先程ツイートしたスマホのも無事投稿できたようです(この画面をもう少し有効活用したいなあ、とは思ってます):
IMG_0744


実際のツイッタークライアントからも投稿されていることが確認できます:
2017051401


と、まあこんなシンプルな感じです。小さな集まりの中で画力対決みたいな企画を開く際にも(ハッシュタグを統一しておけば)簡単に集計できるし、意外と使えるかな、、と評価しています。自分自身は「絵心ないエンジニア」ですが、落書きは嫌いじゃないので、意外と楽しんで使えそうです(自信作の「宇宙選択ヤマト」です):
2017051400


今回はツイッタークライアントとして作りましたが、実用面を考慮すると LINE とかのほうが使われる機会多そうかなあ、とか、インスタとはユーザー層が違いそうだなあ、、とか考えてます。 まだ「とりあえず動く」レベルで公開しただけなので、使っていただける皆さんからの要望などあればいただきたいです。


マンホールマップに「スマホからもっと簡単に投稿できるようにしたい」という要望に応える新機能を用意しました。具体的には Twitter から投稿可能にしました。というわけで、以下の機能を使う前提として、スマホに Twitter アプリが導入されている必要があります。


また、この機能を使うには、Twitter で @Manholemap_Bot をフォローしてください( #manhotalk_bot と似ていてややこしいですが間違えないでくださいw)。この機能のために作成した新しいボットのアカウントです:
2017030801


試しに三鷹のこのマンホールを投稿してみることにします。この画像がスマホの中に保存されているものとします:
mitaka


お持ちの各種スマホ(やPC)から、フォローした上記アカウントへのメンションでマンホール画像を送付してください。メンションとはメッセージの頭に @Manholemap_Bot (大文字小文字は区別しないので、全て小文字でもOKです)を付けて、画像を添付して投稿してください:
IMG_0365


基本的にスマホ側での作業はこれだけで投稿できます。以下はPCでの作業を想定しています。少し(最大5分)待つと、投稿した画像がマンホールマップに反映されます:
2017030802


投稿した本人(と同じ Twitter アカウントでログインした状態)がその画像ページにアクセスした場合は編集ボタンが表示され、投稿の編集が可能になります:
2017030803


位置やテキストなど、必要に応じて編集して、最後に「更新」します:
2017030804


残念ながらまだいくつかの制約事項があります:
(1) テキストを同時にツイートできない
(2) 元の画像に位置情報が含まれていても反映されない(Twitter の仕様)

色々調査しながらにはなりますが、今後のアップデートで少しずつ便利にしていくつもりです。


なお、この機能はアプリケーション開発者向けに公開しているマンホールマップ API を使って作成したものです。誰でも使えるものなので、興味をお持ちの方はこちらから仕様書をどうぞ:
http://manholemap.juge.me/dev.jsp



Node-REDnode-red-node-twitter ノードを使って、ツイッターのリアルタイム検索を行い、その結果を表示するウェブアプリケーションを作ってみました。Node-RED 環境に node-red-node-twitter ノードを追加することで以下の作業が可能になります。或いは IBM Bluemix から Node-RED スターターを使って作成した環境であれば、はじめから同ノードが組み込まれているため利用可能です。

まずは Node-RED のキャンバス内に以下のようにノードを配置します:
balloon_nodered


Twitter のノードにはリアルタイム検索を行うキーワードを指定しておきます。以下の例では "iPhone" というキーワードで Twitter 内をストリーム検索するように指定しています:
2017020501


そして WebSocket 出力ノードでは出力先を /ws/tweets に指定しています。ここは任意の文字列でもいいのですが、後述の HTMLテンプレートの内容が "/ws/tweets" の WebSocket を監視するような内容になっているので、これらの内容を一致させる必要があります:
2017020502


また HTTP 入力ノードでは GET の /tweets を指定しています。つまりウェブブラウザで /tweets というページを参照した時にここで定義するページ内容が表示されるようにしています:
2017020503


その際のページ内容をこちらのテンプレートノードで定義しています。以下のように HTML が指定されており、それがそのまま出力されます:
2017020504


このテンプレートノードの中身は、こちらの template ファイルの内容をそのままコピー&ペーストしてお使いください:
https://github.com/dotnsf/balloon_tweets


※なお、上記で紹介したのとまったく同じノード構成をこちらに用意しておきました。自分でノードを構成しなくても(単に動かしたいという目的だけであれば)この JSON ファイルの内容を Node-RED にインポートして使っていただいてもかまいません:
http://dotnsf.blog.jp/balloon_tweets.json


ノード構成の準備ができたら、Node-RED 画面右上のボタンでデプロイします:
2017020505


デプロイが成功するとここで定義したノードが動き出し、指定したキーワード(今回の場合は "iPhone")で Twitter のリアルタイムツイート検索が行われます。該当するツイートは画面右の debug タブ内に次々と表示されていきます:
2017020506


この様子をもう少し見やすくしたのが HTML テンプレートです。Node-RED と同じホストを指定して、http://(Node-RED のホスト)/tweets をウェブブラウザで開くと、検索されたツイートが画面内に次々と吹き出しを伴って表示されていく様子を確認できます:
2017020500


実際にツイートが次々と追加されていく様子はこちらの動画を御覧ください。"iPhone" くらいに頻度の高いワードで検索すると、こんな感じのスピードでツイートされている、というのが分かる動画になっています:




先日、マンホールマップのログイン機能に障害が発生しました:

通常、マンホールマップ(PC版)のトップページにアクセスすると、画面右上には "Login with Twitter" マークが表示されます(未ログインの場合):
2015122801


で、ここをクリックして、Twitter の OAuth 認可によってマンホールマップにログインして・・・という流れでログインするのですが、ここに問題が発生していると「現在、何らかの障害によってログインできません。」というメッセージが表示されます:
2015122802


一般的には Twitter API の障害が発生していることを示すメッセージなのですが、先日マンホールマップで発生していたものは Twitter API には障害が発生していないにも関わらず、上記のようなメッセージが表示されていたのでした。 ちなみに Twitter API の稼動状態はこちらで確認できます:
API Status | Twitter Developers


さて、上記の原因はなんだったのでしょうか? ちなみにマンホールマップは Java で開発された Web アプリケーションで、この Twitter の OAuth 認可の部分は Twitter4J を使って実装しています(最新版の 4.0.4 でも、スナップショット版の 4.0.5 でも再現)。で、問題部分のコードは以下のようになっていました:
String authorizationURL = null;
try{
  Twitter twitter = new TwitterFactory().getInstance();
  twitter.setOAuthConsumer( TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET );
  RequestToken requestToken = twitter.getOAuthRequestToken();  ←ここで Exception 発生

  String token = requestToken.getToken();
  String tokenSecret = requestToken.getTokenSecret();
    :
    :
}catch( Exception e ){
  e.printStackTrace();
}

リクエストトークンを取得するための Twitter.getOAuthRequestToken() メソッド実行時に Exception が発生していました。それまでに設定しているのは Twitter API の Consumer Key と Consumer Secret だけで、これは正しい値が設定できていました(というか、正しく動いていた時から何も変えてません)。 で、その下の catch 内で以下のようなスタックトレースが出力されていました:
Invalid signature on ECDH server key exchange message
Relevant discussions can be found on the Internet at:
        http://www.google.co.jp/search?q=3cc69290 or
        http://www.google.co.jp/search?q=45a986b4
TwitterException{exceptionCode=[3cc69290-45a986b4 3cc69290-45a9868a], statusCode=-1, message=null, code=-1, retryAfter=-1, rateLimitStatus=null, version=4.0.5-SNAPSHOT(build: 9b7efa6faa540a9defb3e6ba9122a356155986b1)}
        at twitter4j.HttpClientImpl.handleRequest(HttpClientImpl.java:179)
        at twitter4j.HttpClientBase.request(HttpClientBase.java:57)
        at twitter4j.HttpClientBase.post(HttpClientBase.java:86)
        at twitter4j.auth.OAuthAuthorization.getOAuthRequestToken(OAuthAuthorization.java:115)
        at twitter4j.auth.OAuthAuthorization.getOAuthRequestToken(OAuthAuthorization.java:92)
        at twitter4j.TwitterBaseImpl.getOAuthRequestToken(TwitterBaseImpl.java:292)
        at twitter4j.TwitterBaseImpl.getOAuthRequestToken(TwitterBaseImpl.java:287)
        at me.juge.geoimgweb.AppInfo.GetAuthorizationURLTwitter(AppInfo.java:361)
        at me.juge.geoimgweb.AppInfo.GetAuthorizationURL(AppInfo.java:350)
        at org.apache.jsp.stats_jsp._jspService(stats_jsp.java:928)
        at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
        at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:377)
        at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
        at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:122)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
        at java.lang.Thread.run(Thread.java:745)
Caused by: javax.net.ssl.SSLKeyException: Invalid signature on ECDH server key exchange message
        at sun.security.ssl.HandshakeMessage$ECDH_ServerKeyExchange.(HandshakeMessage.java:1098)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:278)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:913)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:849)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1035)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1344)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1371)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1355)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1093)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:250)
        at twitter4j.HttpClientImpl.handleRequest(HttpClientImpl.java:137)
        ... 30 more


問題の getRequestOAuthToken() を実行した時に "Invalid signature on ECDH server key exchange message" というエラーメッセージが出力されています。何コレ?"Invalid signature" って言われても、この時点ではこちらは Key と Secret 以外何も指定してないので間違えているとは思えないけど・・・??

しつこいようですが、今までは何の問題もなく動いていたコードが、ある時を境に急にこのような挙動になってしまいました。となると、Java ソースコードに直接の原因があるとは考えにくい・・・


しかもこの問題、何が厄介かというと、デバッグしようとしてローカルの Eclipse 内で動かすと、このエラーも Exception も発生しないのです。ちゃんと動いてしまいます。更に言うと、別のサーバー内に同様の Java + Tomcat 環境を作って、同じ war をデプロイして実行しても発生しない。要はこの本番サーバーだけで発生するエラーだったのです。。 となると環境依存か、原因特定は更に厄介だぞ・・・

とりあえず緊急でサーバーを1つ作り、そちらに Java と Tomcat を導入し、アプリの war をデプロイ、したら動きました。なので、応急処置としてはこれでなんとかなります。この応急処置サーバーで運用を続けながら原因追求と解決を続けます。 ああ、クラウドってこういう時に便利なのね。。。


さて、このエラーメッセージをググってみると分かるのですが、SSL 関連の Exception のようなのです。はて、マンホールマップに SSL 関連モジュールなんか使ってたっけ?? 何よりも仮に使っていたとしても、実行コードは Twitter4J の中だし、指定しているのは Key と Secret だけで変えようがないし、他のサーバー環境だと動いちゃうし・・・ 念のため API Key と Secret を新たに取得し直して見ましたが、状況は変わりませんでした。


次に行ったのは JDK と Tomcat のアンインストール、および再インストールです。環境は CentOS だったので、 JDK のアンインストールは以下の手順で行いました:
# yum list installed | grep jdk  (インストール済み JDK を確認)
java-1.7.0-openjdk.x86_64
java-1.7.0-openjdk-devel.x86_64
jdk.x86_64              2000:1.7.0_79-fcs (候補は3つ、全部削除してもよいと判断)

# yum remove java-1.7.0* (上2つをアンインストール)
# yum remove jdk* (一番下をアンインストール)

同様にして、Tomcat も以下の手順でアンインストールしました:
# yum list installed | grep tomcat (インストール済み tomcat を確認)
tomcat6.x86_64          6.0.24-90.el6   @base
tomcat6-admin-webapps.x86_64
tomcat6-el-2.1-api.x86_64
tomcat6-jsp-2.1-api.x86_64
tomcat6-lib.x86_64      6.0.24-90.el6   @base
tomcat6-servlet-2.5-api.x86_64 (候補は6つ、全部削除してよいと判断)

# yum remove tomcat6* (まとめてアンインストール)

でもってそれぞれを再インストールします:
# yum install java-1.7.0-openjdk-devel
# yum install tomcat6 tomcat6-admin-webapps

これで Java と Java アプリケーションサーバーはまっさらな状態に戻りました。この状態でマンホールマップの JAR をインストールしてアクセスしたところ・・・ 何も変わらない・・・ orz


全く原因が思いつきません。いよいよヤバい。 改めてこの ECDH なるものを調べてみると暗号化のアルゴリズムっぽいようでした:
ECDH アルゴリズムの概要

暗号化アルゴリズムの、鍵の交換の所で(しかも今まで動いていたものが、ある時から)動かなくなった、ということになります。いよいよ OS レベルの中身がぶっ壊れてしまったか?? と思ったのですが、最後の希望を託してインストールした全ライブラリを更新してみることにしました:
# yum update -y
  :
  :
  :

# /etc/init.d/tomcat6 restart

で、Tomcat をリスタートしてアクセスしてみたら・・・ 直りました! 助かった!!
2015122901
 ↑直ったのはついさっき


後は緊急運用サーバーからこちらに切り替えれば対処完了(予定)。 結局「これ」という直接の原因は分からなかったけど、古いライブラリでは対応しない機能を使っていた、ということなんでしょうかね~。

自分が作って運用しているウェブサービスの多くはクラウド上で動いてますが、このマンホールマップに関しては自宅サーバーで(更に言えば ThinkPad で)運用することにまだこだわっています。今回のエラーはその信念を揺るがすものでしたが、とりあえず解決できました。もうしばらく自宅サーバー運用を続けることができそうです。 v(^o^)



久しぶりに Twitter API キーやアクセストークンを新規に取得しようとしたら、それまで知っていた手順と違っていたので、改めて書き残しておきます。

まず以下のサイトへ行きます。ログインしていない場合は右上に "Sign in" というリンクが表示されるので、そこからログインします:
Twitter Application Management


Twitter Application Management サイトにログインすると、自分が過去に作った Twitter アプリの一覧が表示されます(まだ作ったことがない場合は何も表示されないはずです)。新たに API キーを取得するには新しいアプリを1つ定義します。画面右上の "Create New App" と書かれたボタンをクリックします:
2015111101


新規に作成するアプリケーションの概要を入力します。このうち "Name" はアプリケーションの名称ですが、全半角スペースを含むことができません。また、既に誰かが使っている名称を使うこともできません。1語の単語の形でユニークな名称を指定してください。"Description" はアプリケーションの説明文ですが、空にはできません。"WebSite" はアプリケーションのウェブサイト URL でこれも必須ですが、Streaming API などウェブサイトの存在しないアプリを作る場合もあると思います。ここは http://127.0.0.1 で始まるような名前を指定しても動作には支障なさそうでした。Callback URL は OAuth のコールバック先ですが、必須ではありません:
2015111102


全て入力したら下にスクロールして、Developer Agreement を確認し、内容に同意できる場合は "Yes, I agree" にチェックを入れて、最後に "Create you Twitter application" ボタンをクリックします:
2015111103


指定したアプリケーションの API キーが作成されます。API キーを確認するには、"Keys and Access Tokens" タブを開くと、その中に API Key や API Secret などが表示されているはずです:
2015111104


一方、アクセストークンはこの段階ではまだ作成されていないはずです。アクセストークンが必要な場合はこのページを下にスクロールして、まだ作成されていないことを確認した上で "Create my access token" ボタンをクリックします:
2015111101


処理が成功すると同ページの内容が書き換わり、アクセストークンとアクセストークンシークレットが確認できるようになります:
2015111102



以前の方法だと、最初は https://dev.twitter.com/ からスタートしたような記憶があるんだけど、今はそこからだと API Key 取得ページへのリンクが見当たらないんだよな、、変わったのかな?


このページのトップヘ