とっても簡単!無料でフォームを作れるサービス!JotForm

最近ちょっと面白いサービスをみつけたのでメモ

Web制作で、フォーム作成って毎回めんどう

Web制作で、お問い合わせフォームなどのフォーム作成って良くあると思いますが、案外面倒だったりします。
大体の制作の方は、独自でライブラリ化していたり、WordPressだとContact Form 7なんかを使っていたりしますよね。
それだけでも効率的で作業も早いのですが、もっと手軽にフォームを作れるサービスがあるのです。

JotForm

このJotFormというサービスは、びっくりする程簡単にフォームが作れてしまいます。
しかもGoogle DriveやDropboxとも連携出来るので、かなり便利!
無料版だと100件までしか投稿できないようなのですが、それでもあまりある機能と設定ができます。
簡単な説明はこの動画でなされていますので、参考までに。

JotForm in 2 Minutes from JotForm Form Builder on Vimeo.
では簡単にですが、実際にフォームを作ってみたので手順を紹介してみます。
なお、サインアップのフローは簡単なので省きます。

CreatFormでフォームを作成する

・ログインすると画面上部にメニューが表示されるので「MY FORMS」というメニューを押します。
すると以下の様な画面が表示されるはずです。
jotform1
・次に、左上にある「Create Form」を押してみます。
すると、下図のようなフォームタイプを選択するダイアログが表示されます。
jotform2
・用意されたテンプレートからもフォームを作成できますが、今回は「Blank Form」で作成します。
「Blank Form」を選択すると、フォーム作成画面が開きます。

フォームに要素を入れていく

・左側にツールメニューがありますので、自分が作りたいフォームの要素をクリックしましょう。そうすると、編集画面に要素が追加されるのでお好みでラベルを編集したり、要素の順序を変更します。
作成し終わったら、「Save」を押して保存します。
・私が試しに作ったフォームは下図のような感じです。このくらいなら所要時間1分足らずでぱぱっとできちゃいますね。
jotform4

作成したフォームをWordPressに埋め込んでみる

・作成したフォームを実際に使用してみます。
せっかくなのでこのエントリに埋め込んでみましょう。
・フォーム作成画面中段に「Setup & Embed」タブをクリックし、「Embed Form」というボタンを押すと、下図の様なウィンドウが表示されます。
jotform5
・このウィンドウにある「Embed」もしくは「WordPress」を押しましょう。
すると埋め込み用のコードが表示されるので、それをコピーして投稿に貼付けます。
・埋め込んだフォームから実際に投稿してみてください。

投稿した内容を確認する

次に投稿した内容を確認してみましょう。「MY FORMS」ページの一覧で、作成したフォームを選択し下図のように「View Submissions」を選択します。
jotform8
「View Submissions」ページを表示すると、フォームに投稿されたメッセージなどがここで一覧されます。おそらく下図のようになっているはずです。
jotform9
この内容は、ユーザーから投稿された際にメールでお知らせする設定にもできるので、このページで確認する限りではありません。
どうでしょうか?ざっくりとフォーム作成をしていましたが、実際に作ると恐ろしく簡単にできてしまうので、ここで説明するまでも無いかもしれませんね。
また、冒頭で書きましたが、DropBoxとの連携も簡単にできます。自身のDropboxアカウントと連携すると「JotForm」というフォルダが作成され、そこに投稿データが蓄積されていました。詳しくは下記ページの動画を見てください。
http://www.jotform.com/dropbox/
あと、今回作ったフォームはこのエントリーに貼ってみました。↓こんな感じに成ります。

PHPでXML-RPCを使ってWordPressに画像を含めた記事を投稿する方法 〜 その2

前回BlogIDを取るところまでやったので、その続きです。

画像をWordPressへアップロードする

XML-RPC APIで、ローカルにある画像をサーバへ送信します。
とりあえず送信してURL取るところまでのコードです。

$imageFile = 'hoge.png';
$imagePath = '/images/' . $imageFile;
$image = file_get_contents($imagePath);
$extension = pathinfo($imagePath, PATHINFO_EXTENSION);
$file = array(
    'bits' => new XML_RPC_Value($image, 'base64'),
    'name' => new XML_RPC_Value($imageFile, 'string'),
    'type' => new XML_RPC_Value('image/' .$extension, 'string'),
);
$wpFile = new XML_RPC_Value($file, 'struct');
// ファイルのアップロード
$message = new XML_RPC_Message(
    'metaWeblog.newMediaObject',
    array($blogId, $wpUserName, $wpPassword, $wpFile)
);
$wpResult = $cli->send($message);
if (!$wpResult) exit('Could not connect to the server.');
if( $wpResult->faultCode() ) exit($wpResult->faultString());
$resp = XML_RPC_decode($wpResult->value());
$uploadImageUrl = $resp['url']; //アップロードしたファイルのPath

ではコードを見ていきます。

【構造体データを作る】


XML_RPC_Message
でサーバへデータを渡すために、XML_RPC_Valueで構造体データを作ります。
blogger.getUsersBlogsでは配列でデータを渡しましたが、今回は連想配列になっていますね。
これは、XML_RPCの構造体データを作る場合は、連想配列にしなくてはいけないようです。
作成した連想配列を更に「new XML_RPC_Value($file, ‘struct’)」として構造体データにしています。
また、bitsデータの第2引数にbase64を取っていますが、これを指定するとBase64エンコードが透過的におこなわれます。

$file = array(
    'name' => new XML_RPC_Value($imageFile, 'string'),
    'bits' => new XML_RPC_Value($image, 'base64'),
    'type' => new XML_RPC_Value('image/' .$extension, 'string'),
);
$wpFile = new XML_RPC_Value($file, 'struct');

それと上記補足ですが、サーバへアップロードするだけなら、typeは必要無いですが、これが無い場合メディアの一覧にサムネイルが表示され無かったので追加しています。

【画像をアップロード(転送)】

BlogIDを取得した時と同様、XML_RPC_Messageでリクエストを作り、XML_RPC_clientsendメソッドで送信します。
また、画像の転送にはmetaWeblogAPInewMediaObjectメソッドを使います。

$message = new XML_RPC_Message(
    'metaWeblog.newMediaObject',
    array($blogId, $wpUserName, $wpPassword, $wpFile)
);
$wpResult = $cli->send($message);

【アップロードした画像のURLを取得】

画像を転送したら、これもBlogID取得時と同様に、レスポンスをXML_RPC_decodeで変換し、URLを取得します。

$resp = XML_RPC_decode($wpResult->value());
$uploadImageUrl = $resp['url']; //アップロードしたファイルのPath

WordPressへ記事を投稿する

最後に先ほどアップロードした画像を新しい記事に載せて投稿します。
以下コードとなります。

$title = 'XML-RPC テスト'; //タイトルを設定
$categories = array( //複数カテゴリを設定
    new XML_RPC_Value('カテゴリ1', 'string'),
    new XML_RPC_Value('カテゴリ2', 'string')
);
$description = <<< EOF
<p>Test Post</p>
<img src="{$uploadImageUrl}">
EOF;
$wpContent = new XML_RPC_Value(
    array(
        'title' => new XML_RPC_Value($title, 'string'),
        'categories' => new XML_RPC_Value($categories, 'array'),
        'description' => new XML_RPC_Value($description, 'string'),
        'dateCreated' => new XML_RPC_Value(time(), 'dateTime.iso8601')
    ),
    'struct'
);
$publish = new XML_RPC_Value(0, 'boolean'); // 0だと下書き状態でPOST
// 新しい記事の投稿
$wpMessage = new XML_RPC_Message('metaWeblog.newPost',
    array($blogId, $wpUserName, $wpPassword, $wpContent, $publish)
);
$wpResult = $cli->send($wpMessage);
if (!$wpResult) exit('Could not connect to the server.');
if ($wpResult->faultCode()) exit($wpResult->faultString());
$postId = XML_RPC_decode($wpResult->value()); // 必要であれば戻り値のPostIDを保存

コードを見ていきます。

【投稿データを作る】

記事に必要なデータを、XML_RPC_Valueで作ります。
今回作成する記事はタイトル、本文、カテゴリ、投稿日時のみですが他にも、descriptionmt_keywords(タグ)、custom_fields(カスタムフィールド)、wp_slug(スラッグ)などを設定できるようです。

$wpContent = new XML_RPC_Value(
    array(
        'title' =&gt; new XML_RPC_Value($title, 'string'),
        'categories' =&gt; new XML_RPC_Value($categories, 'array'),
        'description' =&gt; new XML_RPC_Value($description, 'string'),
        'dateCreated' =&gt; new XML_RPC_Value(time(), 'dateTime.iso8601')
    ),
    'struct'
);

【記事を下書き状態で投稿】

記事データを作ったので、次は投稿を行うのですが、このまま投稿すると公開状態で投稿されます。
なので、投稿を一度確認したいので、下書き状態で保存することにします。
XML_RPC_Messageで投稿データを配列で渡していますが、配列データの5番目の配列($publish)がステータスとなっています。
これは「$publish = new XML_RPC_Value(0, ‘boolean’);」でfaluse(0)をXML_RPCデータでセットしています。

$publish = new XML_RPC_Value(0, 'boolean'); // 0だと下書き状態でPOST
// 新しい記事の投稿
$wpMessage = new XML_RPC_Message('metaWeblog.newPost',
    array($blogId, $wpUserName, $wpPassword, $wpContent, $publish)
);
$wpResult = $cli->send($wpMessage);

【投稿したPostIDを取得】

sendメソッドで投稿完了なのですが、ローカルデータと投稿したデータを紐付けたい場合があるので、一応PostIDを取っておきます。

$postId = XML_RPC_decode($wpResult->value()); // 必要であれば戻り値のPostIDを保存

簡単にWordPressへ投稿できましたが、そもそものXML-RPCへの理解が乏しかったので、PEARライブラリのドキュメントを良く読まないとなーといった感じでした。

PHPでXML-RPCを使ってWordPressに画像を含めた記事を投稿する方法 〜 その1

WordPressに外部サーバなどから投稿できないかな?と思い、「そういえばXML-RPC APIがあったなぁ」なんと事を思い出したので、少し調べて見ました。
XML-RPCの仕様については下記参照
XML-RPC 仕様書
今回はPHPのPEARライブラリを使ってみます。
まずはPEARのXML_RPCパッケージをインストールします。

pear install XML_RPC

詳しい使い方はXML_RPCのドキュメントを見てください。

BlogIDを取得する

では実際にコードを書いていきたいと思います。
まず記事を投稿する前に、投稿したいWordpressのBlogIDを取得します。

<?php
require_once('XML/RPC.php');
$hostName = 'wp.example.com';
$xmlrpcPath = '/xmlrpc.php';
$appKey = '任意文字列(空で良い)';
$user = 'ユーザー名';
$passwd ='パスワード';
$wpAppKey = new XML_RPC_Value($appKey, 'string');
$wpUserName = new XML_RPC_Value( $user, 'string');
$wpPassword = new XML_RPC_Value( $passwd, 'string');
$message = new XML_RPC_Message(
    'blogger.getUsersBlogs',
    array($wpAppKey, $wpUserName, $wpPassword)
);
$cli = new XML_RPC_client($xmlrpcPath, $hostName, 80);
$wpResult = $cli->send($message);
// 戻り値0はI/Oエラー
if (!$wpResult) exit('I/O Error. '.$cli->errstring);
// faultCode(),戻り値0以外は失敗
if ($wpResult->faultCode()) exit($wpResult->faultString());
$blogs = XML_RPC_decode($wpResult->value());
$blogId = new XML_RPC_Value($blogs[0]['blogid'], 'string');

コード内容を説明しておきますね、若干コードが前後します。

【サーバに送信するリクエストの作成】

まずWordPressに接続するため、XML_RPC_Messageインスタンスを作ります。

$message = new XML_RPC_Message(
    'blogger.getUsersBlogs',
    array($wpAppKey, $wpUserName, $wpPassword)
);

XML_RPC_Messageはサーバに起動したいメソッドとパラメータを送信し、戻り値としてXML_RPC_Responseを受け取ります。
今はBlogIDが欲しいのでBlogger APIgetUsersBlogsメソッドを設定します。
第2引数には、接続に必要なユーザー情報を配列で渡していますが、これは予めXML_RPC_Valueメソッドで作成します。

$wpAppKey = new XML_RPC_Value($appKey, 'string');
$wpUserName = new XML_RPC_Value( $user, 'string');
$wpPassword = new XML_RPC_Value( $passwd, 'string');

先頭のappkeyBlogger APIで利用するもののようですが、WordPressでは必要無いので任意の文字列または空で良いみたいです。
XML_RPC_Valueの第2引数にはXML_RPCの型を入れます。

【サーバへリクエストを送信する】

先ほど作成したXML_RPC_Messageインスタンスを、XML_RPC_clientsendメソッドに渡して、サーバへ送信します。

$wpResult = $cli->send($message);

【エラー確認】

sendメソッドは戻り値としてXML_RPC_Responseを返しますが、戻り値0の場合はI/Oエラーです。

// 戻り値0はI/Oエラー
if (!$wpResult) exit('I/O Error. '.$cli->errstring);

次に、faultCodeメソッドで判定します。0以外の戻り値は失敗となります。

// faultCode(),戻り値0以外は失敗
if ($wpResult->faultCode()) exit($wpResult->faultString());

【取得したデータをPHPデータ型に変換】

valueメソッドでサーバから返されたXML_RPCオブジェクトを受け取り、さらにXML_RPC_decodeでPHPのネイティブデータ型に変換します。
逆に、PHPのネイティブデータ型をXML_RPCオブジェクト型に変換する場合は、XML_RPC_encodeを使用します。

$blogs = XML_RPC_decode($wpResult->value());
$blogId = new XML_RPC_Value($blogs[0]['blogid'], 'string');

これで変換されたデータからBlogIDを取得できますが、このあとの投稿時に利用するので、XML_RPC_Valueでカプセル化しておきます。
ちょっと前置きが長くなったので、続きはその2に書きます。

Python ftplibでファイルをFTP GETする

最近お目にかからなくなってきたFTPサーバから、データをGETするスクリプトを組んでたのでメモ。
ftplibを使って、指定ディレクトリからデータをGETする簡単なサンプルコードです。
gzファイル取りたかったのでバイナリモードにしてますが、ASCIIモードの場合はretrlines()で。
下記サンプルコードではファイル名固定ですが、「あるディレクトリ配下のすべてのファイルをGET」とかする場合は、nlst()が便利だと思う。

ftp_srv.cwd(archive_path)
for getfile in ftp_srv.nlst():
    ...
    ...

NLSTの結果をリストオブジェクトで返してくれるので操作しやすい。

# coding=utf-8
import os
import ftplib
from ftplib import FTP
if __name__ == '__main__':
    archive_path = 'archive/20130619'
    filename = 'service.log.gz'
    try:
        ftp_srv = FTP('FTPサーバ', 'ユーザー名', 'パスワード') #FTPサーバへ接続
        ftp_srv.cwd(archive_path) # カレントディレクトリの指定(archive_pathがカレントになる)
        file_path = '/'.join([archive_path, filename])
        if not os.path.isfile(file_path):
            # バイナリモードでファイルを開く
            with open(file_path, 'wb') as f:
                ftp_srv.retrbinary('RETR '+filename, f.write) # バイナリモードでファイルを受信
        ftp_srv.quit()
    except ftplib.all_errors, e:
        print '%s' % e

pythonからだと意外とFTPは扱いづらいというか、プリミティブな感じがしました。
SCPとかSFTPならparamikoやFabric使えば良いっぽいし、ftplibは今後使うことあるのかどうか・・・。

EC2インスタンスが突然起動しなくなったのでメモ

以前投稿した、「Python Botoでスケジューリング」的なので回してたサーバが突如起動しなくなりました。
何度か起動を試みましたが、Stateが「Pending」から進まず結局「Stopped」になってしまいます。
業務で必要なサーバだったので、取り急ぎ以下の方法で実施。
1 該当のEIPをDissassociate Address
2 起動しないインスタンスから「Create Image(EBS AMI)」
3 諸事情によりPrivateIPが変更できないので、起動しないインスタンスを一旦Terminate
4 AMIsで先ほど作ったイメージから新たにLaunch
5 起動したインスタンスに、先ほどTerminateしたインスタンスのPrivateIP付与
6 EIPを起動したインスタンスに付与
以上
よくよく考えたら、別のインスタンスからこのVolumeマウントできるか試しても良かったなと思いました。もう後の祭りだけど。

Nginxでgzip化した状態でcacheをしてみる

Nginxのキャッシュはproxy_cacheによって、比較的簡単に取ることができるのですが、できればgzip化された状態でキャッシュしたいなぁと思ってやってみました。
1サーバでNginx + Apacheを想定して設定してみました。
これはあくまでgzip化した状態でキャッシュできるというだけで、パフォーマンスについては調査してません。
これをやってパフォーマンスが低下しても責任はもてませんのであしからず。

下記の設定をみればそのままですが、やってることは結局
(Nginx[80]でキャッシュ) (Nginx[8080]でgzip化) (Apache[10080]アプリケーション)
というだけです。
実際のキャッシュの中身を見ると

KEY: http://www.example.com/
...
Content-Encoding: gzip

めでたくgzip化された状態でキャッシュできました。
以下、端折ってますが設定例です。

http {
    ...
    ...
    proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=zone1:4m inactive=1d max_size=100m;
    proxy_temp_path /tmp/nginx_temp;
    upstream backend {
        server localhost:10080;
    }
    upstream gzip_backend {
        server localhost:8080;
    }
    server {
        listen 80;
        server_name www.example.com;
        location / {
            ...
            proxy_cache zone1;
            proxy_cache_key "$scheme://$host$request_uri$is_args$args";
            proxy_cache_valid 200 60m;
            proxy_cache_valid any 1m;
            proxy_pass http://gzip_backend;
    }
    server {
        listen 8080;
        location / {
            ...
            gzip on;
            gzip_http_version 1.0;
            gzip_vary on;
            gzip_comp_level 5;
            gzip_types text/xml text/css text/javascript application/xhtml+xml application/xml application/rss+xml application/atom_xml application/x-javascript application/x-httpd-php;
            proxy_pass http://backend;
        }
    }
}

Nginxのキャッシュとバーチャルホスト設定で気をつけたい事 メモ

Nginxで複数ドメインを扱う時に、気をつけたい事があります。それはキャッシュの設定。
公式を見る限り、proxy_cache_keyのデフォルトは

$scheme$proxy_host$request_uri

などとなっていますが、この設定を変更せずに複数ドメインを扱うとハマるというお話です。
具体的には以下のような設定の場合

server {
    listen       80;
    server_name  example.com *.example.com;;
    location / {
        ......
        ......
        proxy_cache zone1;
        proxy_cache_key $scheme$proxy_host$request_uri;
        proxy_cache_valid  200 1d;
        proxy_cache_valid  any 1m;
        proxy_pass http://backend;
        ......
        ......

この場合、「http://example.com」や「http://www.example.com」など複数ドメインで同一のキャッシュを参照する様になってしまいます。
実際のキャッシュの中身を見てみると・・・

KEY: httpbackend/

あなおそろしや・・・Nginxのキャッシュは手動で消す(or モジュール入れる)しか方法が無いため、複数ドメインで同じキャッシュが見えてしまう状態になってしまいます。
では、次にproxy_cache_keyを変更してみます。

proxy_cache_key "$scheme://$host$request_uri$is_args$args";

nginxを再起動し、キャッシュをクリアして再度キャッシュの中身を見てみます。

KEY: http://www.example.com/

今度はしっかりとドメイン名でキャッシュできました。
この辺りはハマりどころなので気をつけたいところですね。
ちなみに(複数サーバの)Nginxキャッシュの削除は、Python Fabricで簡単に行えるようにしております、Fabricマジ便利!

Pythonで設定ファイルを読み込みたい場合の方法 ConfigParser

Pythonでコードを書いていると設定する項目が増えてきていろいろと大変・・・。
別のファイルにまとめられないかなと思ったら、ConfigParserなる便利なモジュールがあったので使ってみました。
テストで使用した設定ファイルの内容は以下のものとなります。
FileName:config_file

[options]
spam: egg
hoge: huga
[example]
host=192.168.1.10
port=22
user=iiwake

[]で括られているのが各セクションとなり、それ以下がオプションとなります。
オプションは、左がKeyで右がValueとなっており、「:(コロン)」「=(イコール)」が使用出来るようです。
では実際に読み込んでみます。
read()にて、用意した設定ファイルを指定し読み込みます。

>>> import ConfigParser
>>> conf = ConfigParser.SafeConfigParser()
>>> conf.read('config_file')

get(‘セクション名’, ‘オプション名’)で値を取り出せます。

>>> conf.get('options', 'spam')
'egg'

sections()で各セクション名が返されます。また、options(‘セクション名’)でセクション内の各オプションを返します。

>>> for section in conf.sections():
...     for option in conf.options(section):
...         print '[section: %s] option: %s' % (section, conf.get(section, option))
...
[section: options] option: egg
[section: options] option: huga
[section: example] option: 192.168.1.10
[section: example] option: 22
[section: example] option: iiwake

items(‘セクション名’)はオプション名を指定せず(key, value)のペアで取り出せます。

>>> for section in conf.sections():
...     print 'section: %s, option: %s' % (section, conf.items(section))
...
section: options, option: [('spam', 'egg'), ('hoge', 'huga')]
section: example, option: [('host', '192.168.1.10'), ('port', '22'), ('user', 'iiwake')]

has_option()を使えば、オプションの有無がわかります。

>>> for section in conf.sections():
...     if conf.has_option(section, 'spam'):
...         print conf.get(section, 'spam')
...
egg

意外と簡単に設定の外部化ができました。
ただ、値を複数持つオプション等が出来ないようなので(そこまで調べてませんが)そう言った事をする場合、自前で解析しなくてはならないかも。
 

サーバ上でコマンドラインからWebサイトのスクリーンショットを撮ってみた wkhtmltoimage

Linuxサーバ上で、コマンドラインからWebサイト(Webページ)のスクリーンショットというか、キャプチャーみたいなものが取れないものかと思い探してみたら、
wkhtmltoimageなるコマンドがあったので試してみました。
試したのはAWS EC2のUbuntu Server 12.10(64bit)上
このツールは元々wkhtmltopdfというツールから派生して作成されたようで、APTでインストールする事が可能でした。

$ aptitude install wkhtmltopdf

 
さて、本題のwkhtmltoimageですが、まず下記サイトからDLするのですが、最新Verだとうまく実行できなかったので、古いVerを利用しました。
https://code.google.com/p/wkhtmltopdf/
古いVerのデータが上記サイトから何故か辿れなかったので、とりあえず直接DLします。

$ wget http://wkhtmltopdf.googlecode.com/files/wkhtmltoimage-0.10.0_rc2-static-amd64.tar.bz2

検証したサーバはUbuntu 64bit版なのでこのファイルをDLしましたが、32bit版であれば恐らく下記の物です。
http://wkhtmltopdf.googlecode.com/files/wkhtmltoimage-0.10.0_rc2-static-i386.tar.bz2
次に、落としたデータを解凍

$ tar lxvf wkhtmltoimage-0.11.0_rc1-static-amd64.tar.bz2

すると「wkhtmltoimage-amd64」というバイナリデータが解凍されるので、これを/usr/local/bin等に「wkhtmltoimage」として移動させます。

$ mv wkhtmltoimage-amd64 /usr/local/bin/wkhtmltoimage

 
これでインストール完了、実際にコマンドラインでスクリーンショットを撮ってみましょう。

$ wkhtmltoimage http://www.google.co.jp screenshot.jpg

実際のスクリーンショットはこんな感じに成ります。
screenshot
このツールには色々オプションがあります。

$ wkhtmltoimage -H

にて確認できるので、色々試すのも良いかと。
ちなみに、私の環境だとwkhtmltopdfの方は何故かxvfbが無いと動きませんでした。なので、下記の様にAPTでインストールして、xvfb-runで仮想フレームバッファから実行しています。

$ aptitude install xvfb
$ xvfb-run wkhtmltopdf http://www.google.co.jp screenshot.pdf