アーカイブ
‘HTTP’ タグのついている投稿
Rubyでcometサーバー作る
2011 年 11 月 22 日
コメントはありません
最近cometとかいう最新技術が流行っているらしいので、eventmachine_httpserverで作ってみた。
ここにサーバーとクライアントの例がある。どっちも50行ぐらいで実装できた。
comet at master from shokai/eventmachine-study – GitHub
サーバー起動して、タイムアウトを10秒に指定。
このサーバーは、POSTされた値を保持して、GETされたら返す。GETに対してはレスポンスを遅らせて返す。
ruby server.rb 8080 10
クライアントを起動。GETしてから25秒後にPOST
ruby client.rb 25
GET(comet) -> wait 25 sec -> POST
* GET
sleep 25 sec
404 ## 10秒経過、切断された
* GET ## 再接続
404
* GET ## 3回目
* POST kazusuke
POST success
200
kazusuke
200 ## 5秒待ってようやく値が返ってきた
kazusuke
* GET
サーバー側のログはこんなんなってた
http server start, port:8080, comet_timeout:10(sec)
load: 2.20 cmd: ruby 11481 waiting 0.45u 0.32s
request_method : GET
path_info : /message
query_str :
post_content :
request_method : GET
path_info : /message
query_str :
post_content :
request_method : POST
path_info : /message
query_str :
post_content : kazusuke
kazusuke
request_method : GET
path_info : /message
query_str :
post_content :
cometサーバー、接続が不安定なクライアントにpush通知するのに便利。
AndroidでHTTP POST
2011 年 5 月 13 日
コメントはありません
targetはAndroid2.3.4、API 10向けにビルドして試した。
参考:
<uses-permission android:name="android.permission.INTERNET" />
import java.io.*;
import java.util.*;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
HttpClient client = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://localhost:8080");
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("message", "ほむ"));
try{
httppost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
HttpResponse res = client.execute(httppost);
ByteArrayOutputStream os = new ByteArrayOutputStream();
res.getEntity().writeTo(os);
Log.v("result", os.toString());
Log.v("status", res.getStatusLine().getStatusCode());
}
catch(Exception e){
e.printStackTrace();
}
リクエストができているかの確認は、EM::HttpServerで見た
gem install eventmachine_httpserver
#!/usr/bin/env ruby
require 'rubygems'
require 'eventmachine'
require 'evma_httpserver'
class Handler < EM::Connection
include EM::HttpServer
def process_http_request
res = EM::DelegatedHttpResponse.new(self)
puts "request_method : #{@http_request_method}"
puts "path_info : #{@http_path_info}"
puts "query_str : #{@http_query_string}"
puts "post_content : #{@http_post_content}"
res.status = 200
res.content = "こんにちは"
res.send_response
end
end
EM::run do
EM::start_server("0.0.0.0", 8080, Handler)
puts "http server start, prot 8080"
end
serial-http-gateway作った
2011 年 2 月 4 日
コメントはありません
シリアルポートをhttpで使えるツールを作った。
ブラウザでhttp://localhost:8783を開くとデータが読める。POSTでデータを送ると書き込める。
webブラウザでロボットを操作する部分がある、OB降臨システムというのを作っているのでその部品として作った。
githubに全部置いた。
■インストールと起動
git clone git://github.com/shokai/serial-http-gateway.git
cd serial-http-gateway
gem install serialport eventmachine eventmachine_httpserver json ArgsParser
Arduino等をMacに接続すると/dev/tty.usb〜〜という名前になる。引数に渡して起動する。
./serial-http-gateway --helpport 8783で起動する。引数-portで変更できる。
./serial-http-gateway /dev/tty.usbserial-A7006Rqn
gemが全て入っていれば実行ファイル単体で動くので、適当なパスが通っている場所にコピーして置くと便利。
sudo cp serial-http-gateway /usr/local/sbin/
■使う
HTTP POSTでシリアルポートに書き込める。
% curl -d 'testtest' 'http://localhost:8783'
HTTP-GETでシリアルポートからのデータが読める。
最近100件の受信データが保存してあって配列で返ってくる。timeに時間が入っている。
curl 'http://localhost:8783'
時間はミリ秒でunixtimeなので、1000で割れば普通のunixtimeになる
[{"data":287,"time":1296767483756},{"data":288,"time":1296767483253},{"data":291,"time":1296767482751},{"data":293,"time":1296767482246},{"data":292,"time":1296767481743},{"data":293,"time":1296767481238},{"data":294,"time":1296767480736},{"data":299,"time":1296767480233},{"data":303,"time":1296767479729},{"data":305,"time":1296767479226},{"data":307,"time":1296767478721},{"data":312,"time":1296767478219},{"data":321,"time":1296767477714},{"data":332,"time":1296767477211},{"data":344,"time":1296767476709},{"data":359,"time":1296767476204},{"data":"\u0000390","time":1296767475701}]
eventmachine_httpserver便利だなー
Androidのギャラリーの「共有」メニューから呼び出せるアプリを作る
2010 年 11 月 5 日
コメントはありません
Androidのカメラで撮影した画像はギャラリーに保存される。そこで画像を選んで共有またはshareなどを選択すると、暗黙的Intent呼び出しで画像を別アプリに渡せる。
Flickrアップローダとか、EvernoteとかDropboxのアプリに写真を渡しているのはこのしくみを使っている。
まずManifest.xmlにintent-filterを追加する。
image/*でもいいが、それだと動画も渡せてしまうので手当たり次第mimeTypeを指定してみる。
<intent-filter>ようするに、アプリに「私はimageをSENDするというアクションを受け取って適当に処理できますよ」と宣言させておくと、
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/jpeg"/>
<data android:mimeType="image/jpg"/>
<data android:mimeType="image/png"/>
<data android:mimeType="image/bmp"/>
<data android:mimeType="image/bitmap"/>
</intent-filter>
他のアプリが「誰でもいいからSENDしたimageを受け取ってくれる人いませんか」と呼び出したときに連携できるようになる。
で、このintent-filterと関連付けられているクラス(普通なら一番最初に生成したjavaクラス)の
onCreate()内でintentを受け取る。
あらかじめ、受け取った画像を表示するためのImageViewを配置しておく。
private ImageView imageView;
this.imageView = (ImageView)this.findViewById(R.id.ImageView01);
暗黙的Intentで呼び出されたのか、普通のIntent呼び出しで起動したのか(つまりホーム画面からアプリのアイコンを押して起動したのか)を判別して処理する。
呼び出し元の名前だけで判別してる
Uri imageUri = null;imageUriにはcontent://media〜〜というパスが入る。これはAndroid内のファイルシステムのパスではないが、Media.getBitmapに渡すとデータを読み出せる。
try{
imageUri = Uri.parse(getIntent().getExtras().get("android.intent.extra.STREAM").toString());
}
catch(Exception e){
e.printStackTrace();
}
if (imageUri != null) {
Log.v("ImageIntent", "暗黙的intentから起動");
Bitmap bmp = null;
try {
bmp = Media.getBitmap(getContentResolver(), imageUri);
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
if (bmp != null) {
this.imageView.setImageBitmap(bmp);
}
}
else{
Log.v("ImageIntent", "普通に起動");
}
とりあえずImageViewに表示だけしておく。
あとEclipseだと日本語が化けるのでterminalからadb logcatしてる。
Bitmapを受け取ったら、後は適当に処理すれば良い。
リサイズとか
public Bitmap resize(Bitmap bmp, float scale){
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
return Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);
}
form/multipart-dataでgyazo.comにアップロードするとか、色々できる。
Manifest.xmlにpermissionを追加
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
public void upload(Bitmap bmp) throws ParseException, IOException{
File dataDir = new File(Environment.getExternalStorageDirectory(), this.getPackageName());
dataDir.mkdirs();
FileOutputStream tmp = new FileOutputStream(new File(dataDir, "tmp.png"));
bmp.compress(Bitmap.CompressFormat.PNG, 80, tmp);
tmp.close();
HttpClient httpClient = new DefaultHttpClient();
HttpPost post = new HttpPost("http://gyazo.com/upload.cgi");
MultipartEntity entity = new MultipartEntity();
entity.addPart("id", new StringBody("your-gyazo-id"));
entity.addPart("imagedata", new FileBody(new File(dataDir, "tmp.png"), "image/png"));
post.setEntity(entity);
post.setHeader("User-Agent", "TestAndroidApp/0.1");
httpClient.execute(post);
}
httpでform/multipart-dataでアップロードするには- apache-mime4j-0.6.jar → http://james.apache.org/mime4j/ からダウンロード
- httpmime-4.0.3.jar → http://hc.apache.org/downloads.cgi の httpcomponents-client-4.0-bin.zip 中に入っている
■参考
このへんが参考になった
mbedで取得したセンサーの値をsinatra+tokyocabinetで保存する
2010 年 4 月 25 日
コメントはありません
まずセンサーのデータを時系列で溜めたり、取り出したりするためのwebサービスをsinatra+TokyoCabinetで作っておいた。
そこにmbedのADコンバータでCdSの明るさを計測、LEDを点灯/消灯 と mbedにEthernetを接続しtwitterにpostするを合体させ、約10秒間隔でセンサーの値をhttp-postしまくった。
一度web APIが出ている場所にマイコンの情報も保存しておけば、あとでマッシュアップできて便利になる。このAPIの下にmbedでもgreasemonkeyスクリプトでも何個でもぶら下げられる。
mbedからwedataに保存しても良かったんだけど、あえて自分で作ってみた。
■mbedで明るさを計測してhttp-postする
作った回路。CdSで明るさを取得し、LEDで明るさを表示する。mbedのRD+,RD-,TD+,TD-ピンにトランス入りEthernetジャックを接続し、LANケーブルを刺した。

プログラム。CdSに入る光量が少なくなると緑色のLEDが光る。http-postはTickerで定期的に呼び出したかったが、Ticker内でHTTPClient.hの関数を使うとmainに戻ってこなくなるので仕方なくmain内で20ループ毎(約10秒毎)にHTTPClient.post()するようにした。stdは使えるけど、pthread等のスレッド機能は無いみたいなのでこれは痛い。
sensor_http_post | mbed
#include "mbed.h"せっかくC++が使えて、mbed.hのAPIはC++っぽいのに、HTTPClient.hはそうでもないみたいだ。
#include "HTTPClient.h"
#include <string>
using namespace std;
Serial pc(USBTX, USBRX);
DigitalOut led1(LED1); // blink
DigitalOut led2(LED2); // blink when http-post
DigitalOut led3(p11); // represent ADC value
AnalogIn adc(p15);
float ain;
HTTPClient http; // use DHCP
const string api_uri = "http://shokai.mag.keio.ac.jp/sensor-storage/shokai/cds/";
const string mbed_name = "shokai-mbed01";
const string sensor_name = "CdS";
void blink_led(DigitalOut led, int num){
for(int i=0; i < num*2; i++){
led = !led;
wait(0.1);
}
}
void post_sensor_value(){
blink_led(led2, 2);
char query[256] = "";
char result[4096] = ""; // 4kb
sprintf(query, "name=%s&%s=%f", mbed_name.c_str(), sensor_name.c_str(), (float)ain);
pc.printf("post:%s => %s\n", query, api_uri.c_str());
http.post(api_uri.c_str(), query, result, 4096);
pc.printf("%s\n", result);
blink_led(led2, 2);
}
int main(void){
led1 = 1;
int count = 0;
while(1) {
ain = adc;
pc.printf("sensor:%f\n", float(ain));
led1 = !led1; // blink LED
if(ain > 0.1) led3 = 0; // represent CdS value
else led3 = 1;
if(count > 20){
post_sensor_value();
count = 0;
}
count++;
wait(0.5);
}
}
mbedのローカルのストレージがfopenで読めるので、そこに設定ファイルを置けるようにしたい。yamlやjsonをparseするライブラリを作りたい。
動作はシリアル通信で見れる。表示にはserialterm.rbを使った。
postすると返り値がjsonで来る。

■tokyocabinetとsinatraでデータを溜めるAPIを作る
できたもの
sensor-storage API
githubでソースを全部公開しておいた。
shokai's sensor-storage at master – GitHub
http://shokai.mag.keio.ac.jp/sensor-storage/ にhttp-postした内容を、json stringにエンコードして時刻_マイクロ秒をキーにしてTokyoCabinetに保存する。
keyの _ の左側が時刻になっているので後で使う時に便利。
http://shokai.mag.keio.ac.jp/sensor-storage/keys で保存されているkeyの一覧がJSON形式で見れる。
http://shokai.mag.keio.ac.jp/sensor-storage/recentで最近10件のkeyとvalueが取れる。他にもrecent/件数や、last(最後に入れたデータ)、count(データ件数)などのAPIがある。
今回のmbedでCdSの値を保存しているのは http://shokai.mag.keio.ac.jp/sensor-storage/shokai/cds/recent になる。shokai/cds/last , shokai/cds/recent , shokai/cds/count などで色々データが取り出せる。
/ 以下に適当に文字列を付けると新しくDBを作るようになっている。もしデータをpostしてみたいなら、mbedの方のコードは/shokai/cds/をハードコーディングしているので、そこを適当に変えてから使ってくれるとうれしい
あまりにも大きいデータは入らない。
スクリプト自体はわりとシンプル。sinatra使って1ファイルに書いてある。
server/main.rb at master from shokai's sensor-storage – GitHub
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'sinatra'
require 'sinatra/reloader' if development?
require 'rack'
require 'json'
require 'tokyocabinet'
include TokyoCabinet
@@dbdir = 'db'
def db_open(dbname='/')
dbname = dbname.to_s.gsub(/\//, '_')
@hdb = HDB.new
Dir.mkdir(@@dbdir) if !File.exists?(@@dbdir)
@hdb.open("#{@@dbdir}/_#{dbname}.tch", HDB::OWRITER|HDB::OCREAT)
end
after do
@hdb.close if @hdb
end
get '*/' do
path = "../" * params[:splat].first.count('/')
redirect "#{path}./readme"
end
get '*/keys' do
db_open(params[:splat])
@hdb.keys.reverse[0...10000].to_json
end
get '/dbs' do
Dir::glob("#{@@dbdir}/*.tch").map{|i|
name = i.to_s.scan(/.*\/(.+)\.tch/).first.to_s.gsub(/_/,'/')
tmp = name.split(//u)
tmp.shift
tmp.to_s + "/"
}.uniq.delete_if{|i|
i =~ /\/\//
}.sort.to_json
end
get '*/count' do
db_open(params[:splat])
{"count", @hdb.rnum}.to_json
end
get '*/last' do
db_open(params[:splat])
key = @hdb.keys.last
{key, @hdb[key]}.to_json
end
get '*/recent' do
db_open(params[:splat])
@hdb.keys.reverse[0...10].map{|k| {k, @hdb[k]} }.to_json
end
get '*/recent/:num' do
db_open(params[:splat])
num = [params[:num].to_i, 1000].min
@hdb.keys.reverse[0...num].map{|k| {k, @hdb[k]}}.to_json
end
get '*/:tc_key' do
db_open(params[:splat])
key = params[:tc_key]
{key, @hdb[key]}.to_json
end
post '*/' do
db_open(params[:splat])
now = Time.now
key = "#{now.to_i}_#{now.usec}"
params.delete(:splat.to_s)
@hdb.put(key, params.to_json)
{key, @hdb[key]}.to_json
end
delete '*/:tc_key' do
key = params[:tc_key]
v = @hdb[params[:tc_key]]
@hdb.out(params[:tc_key])
{key,v}.to_json
end
githubのmiscディレクトリ以下にテスト用にhttp postしたりするためのスクリプトがある。mbedが無い時の動作チェックにどうぞ。
サーバーはthinで10個preforkさせておいて、それぞれ別のportで起動させてある。apache2のmod_proxy_balancerでそこにproxyして、80番ポートのサブディレクトリ下にあるように見せている。
- 橋本商会 apache2のサブディレクトリをthinで起動してるsinatraにプロキシする
- ubuntu8.04にtokyocabinetインストール – 橋本詳解
- Macにtokyocabinetインストール – 橋本詳解
- 開発時に自動リロードする – 橋本詳解
- shotgunでコード自動リロードを有効にする – 橋本詳解

最近のコメント