コンピュータやソフトウェアのあれこれ@道民(&元道民)
Web
IEでAjaxGETリクエストを送ったときにキャッシュされたレスポンスを返してくる件
10月 15th
今回、この状況に直面して初めて知ったのでメモ。
ブラウザ上でAjaxを使用した通信を行う(ブラウザからサーバーへgetリクエストを送る)際に発生しました。
Ajaxで送るgetリクエスト
$.get( "/addpoint" ,
{
"id" : id ,
"pt" : point
},
function(result){callback(result);}
);
このとき生成されるURLは http://hogehoge/addpoint?id=xxx&point=yyy になります。
IEで起こったここと
xxxとyyyの値が以前と同じだった場合(同じURLになる場合)キャッシュしてあるレスポンスを返してくる。
つまり、
IE( °⊿°) < 「URLが同じなんだから結果も同じだよね。」
と判断して、サーバーに聞きにいかずに最初の結果を返し続けるわけです。
(上記で言うと変数resultが常に同じ値-最初にリクエストした時の結果)
ちなみに他のブラウザではこのような処理は行われていないよう。
困ること
確かに無駄な通信は減りますが、URLが同じでもサーバーから違う結果が帰ってくることを期待している場合困ったことになります。
(今回の場合は、サーバー側でパラメーター値分の加算/減算が行われることを想定)
サーバーが異なる結果を用意していても、リクエストが送られないので問い合わせられないわけです。
いやあ、まさか、そんなことがあるなんて思いもよりませんでした。
対策
以下のような対策があるようです。
- POST送信しましょう
- リクエスト毎に変動する値をパラメーターに追加しましょう(サーバー側では捨てられる)
- getリクエストのHTTPヘッダに「If-Modified-Since」を設定しましょう
現在は、「リクエスト毎に変動する値をパラメーターに追加」しています。
パラメーターとしては「1970年1月1日午前0時からのミリ秒」を与えています。
$.get( "/addpoint" ,
{
"id" : id ,
"pt" : point,
"tm" : new Date().getTime() //捨てられる
},
function(result){callback(result);}
);
これでIEでも毎回サーバーにリクエスト送信するようになりました。
改良
無駄な通信が発生しているのでリクエストのHTTPヘッダに「If-Modified-Since」を設定するのがいいのかもしれない。そもそもPOSTにするのがいいのかな。
– 追記
以下のようにIf-Modified-Sinceを設定するようにしました。
$.ajax({
beforeSend : function( req ){
req.setRequestHeader("If-Modified-Since", "Thu, 01 Jun 1970 00:00:00 GMT");
},
cache : true,
type:'GET',
url:'/addpoint',
dataType:'json',
data:{
"id" : id ,
"pt" : point
},
success: function(result) {
callback(result);
}
});
JQueryで用意されている簡易版メソッド$.getではなく、$.ajaxを使うことでリクエストのHTTPヘッダに値を設定できました。
教えてもらったこと
WindowsPhoneアプリケーション開発をする際にも同様のことが発生しているようです。
Javascriptのおかしな挙動(メモ)
10月 13th
今日起きた不思議な現象。
以下のようなJavascriptを書いて動かしていました。
function clear_cokkie(code){
var cokkiecode = Base64.decode($.cookie('hogehoge').split('|')[0]);
console.log('code=' + code + ', cookiecode=' + cookiecode + '*' );
console.log(code + 1);
console.log(cookiecode + 1);
if(code != cokkiecode) {
document.location.href = '/';
}
}
※ 「*」は末尾に余計な空白が入っていないことの確認、「+1」は文字列として認識されていることの確認。
やりたいこと
- 現在のコードをcookieに格納されたコードを比較して異なっていれば画面をリロードする
(サーバーサイドで現在のコードに対する必要な処理が行われ、cookieにコードが格納される) - cookieにはbase64でエンコードした状態で値が入っている
- 一つのcookieキーで複数のパラメーターが|区切りで入っている
期待していたコンソール出力
// 1回目 code=12345, cookiecode=56789* 123451 567891 // 2回目 code=12345, cookiecode=12345* 123451 123451
自分の環境でおこった動作
// 1回目 code=12345, cookiecode=56789 123451 56789 // 2回目 code=12345, cookiecode=12345 123451 12345 // 3回目以降無限リフレッシュ
変数cookiecodeの値が内部でうまく処理されていないようなおかしな動き。
他のマシンのIEや自分の別ブラウザでは問題なく動作していました。
自分の環境は
- Windows7 64bit
- IE9(64bit版 32bit版 両方)
IEの中のJavascriptエンジンがどうかしてしまったんだろうか。
不思議でたまらない・・・
LDD’10/Fall in KUSHIROに参加しました
9月 20th
来る9月18日(土曜)
LDD’10/Fall in KUSHIROに参加してきました。
実は、札幌で行われる勉強会にしか参加したことがなかったので初の勉強会遠征。
セッション
セッションは以下の2つに参加しました。
●「わかったつもりになれるHTML5」佐藤琢哉
HTML5は言葉だけは少し聞いていて、ネットで少し見たりはしたけれど
ほとんど知識がなかったので、このセッションがとてもためになりました。
「今すぐではないけれど、いつか知っていなければいけない時が来る。」
常に勉強することを忘れては生き残っていけないなあ、怠ってはいけないなと
決意を新たにしたのでありました。
●「データベース超入門」二谷聡志
お仕事柄、切っても切れないデータベースとの関係ですが、
私は、個人的にSQLが好きです。
普段、うんうん唸ってSQLを考えることは減ったのだけど、
一生懸命組み立てて、欲しいものが一発で取得できた時の感動というか満足感がたまらない。
今回は「超入門」ということで、普段何気なく使っているSQLを復習したのですが、
やっぱり「SELECT * from コレ where アレ」と書いて取得できた瞬間は楽しかった。
プログラムを初めてやる人にとって、実はSQLはとても魅力的なんじゃないかなぁ。
Sqliteのような、インストールや設定が難しくないDBで、
SQLを実行したら、大量の中から欲しいものが取得するという体験をすると
データというのが身近なものである分、プログラムってすごい!
って気持ちが一番分かりやすく感じられるものなのかもしれないなぁって思いました。
懇親タイム・LT・そのほか
そして、懇親タイム(おやつの時間)には、
北海道開発オフの紹介をさせていただきました。
開発オフスタイルの勉強会の楽しさに触れてくれる人が増えたら嬉しいな、と思います。
# なんだか、すべてをつたえきれなかった気もしますが・・・
最後のLTは、みなさん素晴らしかった。
短い時間でちゃんと伝えられて、なにより、伝えることがあるのが素晴らしい。
いつか、伝えたいものが見つかったらLTやりたいな。
伝えたいことを見つけるためにはまだまだ勉強がたりない。
そして、今回の主催者である釧路OSSのスタッフの皆様。
懇親会で少しお話できたのですが、みなさんとてもいきいきしていました。
とても楽しそうでした。
私もこれから、いろいろな形で、自分が普段勉強していることを
他の人と共有できるいろいろなことに関わっていけたらなと思いました。
ノウハウも経験もないので、最初は勢いだけ・・・かもしれないけど。
周りにすばらしい人がたくさんいる環境にあるのだから、
前に進むことは辞めないようにしよう。
他のセッション
同じくとても見たかったこの3つが見られなかったので、
あとでゆっくりUstをみようと思います。わくわく。
●「釧路OSSへようこそ」斉藤和芳
●「パソコンセキュリティ超入門」池田晃和
●「ケーブリングmoreマニアックス」小岩秀和
Ustみたら、感想を追記する予定。
はてなスターを付けれるようにしました。
7月 26th
僕のこのブログはWordPressで運用していますが、以前は、はてなダイアリーでブログを付けていました。
ついさっき、こんなブログを書いたところ(LEDシリコンアサラトが届いた!)、はてなスターを付けたいと言ってくれた人がいたので、対応してみました。
はてなスターの設置方法は「はてなスターをブログに設置するには」というサイトに設置方法が載っていて、ほぼそのとおりだったのですが、HTMLの構造的な問題でちと苦戦しました。
外部ブログにはてなスターを設置する場合、HTML構造がはてなスターに合っていれば、簡単に設置できるようですが、僕のようにWordPressでデザインテーマを他から引っ張ってきた人間にはちと面倒があります。
まず、参照リンク先にある説明のとおり、JavaScriptを読み込むように、head部を編集しました。
$ vi /[Documentルート]/wp-content/themes/grey-matter/header.php
ヘッダ部(<head>~</head>)の最下部に以下を追記。
※順番が大事です。からなず最初にHatenaStar.jsを読み込むようにして下さい。
<script src="http://s.hatena.ne.jp/js/HatenaStar.js" type="text/javascript"></script>
<script type="text/javascript">
Hatena.Star.Token = '**********************';
</script>
Hatena.Star.SiteConfig = {
entryNodes: {
'div.content2': [ ←content2というクラスで繰り返される、
{ uri: 'h1 a',title: 'h1',container: 'h1'} ←h1タグに対してHatena Starを設置する。
]
}
};
僕が使っていたテーマでは、classで繰り返し表示されるようになっていなかったので、「index.php」と「single.php」の記事タイトルとなるh1タグをdiv.content2で囲いました。※ちゃんとコードなりなんなりを読める人ならもっと楽な方法があるかもしれませんね。
そして、Hatena Starの追加ボタンなどを表示させるためには、そのタグにリンクが貼られていないとだめらしいので、single.phpのh1タグの部分に対して、
<h1><?php the_title(); ?></h1>を
<h1><a href="<?php the_permalink() ?>" rel="bookmark" title="<?php _e('Permanent link to this post','grey_matter'); ?>"><?php the_title(); ?></a></h1>
という変更を加えて対応しました!
とりいそぎ自分用メモ的なです。
Tidy関数を使ったスクレイピングとTwitterAPI(2)-TwitterAPI::update
2月 24th
Twitterに投稿をpostしたり、タイムラインを取得したりすることができるTwitterAPI。
(仕様日本語訳:[観] Twitter API 仕様書 (勝手に日本語訳シリーズ))
PHPでTwitterAPIを使用できるPEARライブラリもあるのだけど(Services_Twitter)
勉強を兼ねて自分でクラスを作成してみました。
今後、タイムライン取得や、返信取得も必要に応じて増やしていく予定。
TwitterAPI::updateを呼び出す
PEARライブラリのHTTPRequestを使用しています。
主な流れは
●引数として渡されたID・PASSWORDでBASIC認証を行う
●POSTデータに投稿内容をセット
●URL( http://twitter.com/statuses/update.xml )にPOSTを使用したHTTPリクエスト送信
●レスポンスコードとして200が返ってくれば投稿成功
となります。
//TwitterAPI.class.php
class TwitterAPI {
// ########## TwitterにPOSTする
public function update( $status , $id , $pass ) {
require_once "HTTP/Request.php";
$api_url = "http://twitter.com/statuses/update.xml";
$req = new HTTP_Request($api_url);
$req->setMethod(HTTP_REQUEST_METHOD_POST);
$req->setBasicAuth($id, $pass);
$req->addPostData("status" , $status );
if(!PEAR::isError( $req->sendRequest())){
return $req->getResponseCode();
} else {
return false;
}
}
}
[プロキシを介してインターネットに接続している場合]
以下のようにして、プロキシ設定を追加します。
$req = new HTTP_Request($api_url);
//...(略)
$req->setProxy("your_proxy", 8888);
//...(略)
if(!PEAR::isError( $req->sendRequest())){
....
TwitterAPIクラス使用方法
自作したTwitterAPIクラスは以下のようにして使用します。
IDとパスワードは呼び出し時に自由に設定できるようにしてあります。
// オブジェクト生成 $tap = new TwitterAPI(); $status = "投稿内容文字列"; $tap->update( $status , ID , PASS );
札幌市 水道凍結情報( http://twitter.com/sap_freezer )
いままで作成したクラスを組み合わせて、TwitterBotを作成しました。
Twitter / sap_freezer
メイン実行クラスは以下のようになっています。
#!/usr/local/php5/bin/php
<php?
// 水道情報局のURL
define('INIT_URL' , "http://www.sweb.co.jp/tenki/cgi/freeze.cgi");
// twitter API POST
define('ID' , "twitterID");
define('PASS' , "twitterPassWord");
require_once "GetHttpSource.class.php";
require_once "Scraping.class.php";
require_once "TwitterAPI.class.php";
// HTMLの取得
$ca = new GetHttpSource();
$data = $ca->get_html(INIT_URL);
// Tidy関数config
$config = array('indent' => TRUE,
'output-xhtml' => TRUE,
'wrap' => 200);
// 取得したHTMLファイルの内容をUTF-8の文字コードで$configで指定した設定で、tidyオブジェクトを作成
$tidy = tidy_parse_string($data, $config, 'UTF8');
// cleanRepairで整形
$tidy->cleanRepair();
// スクレイピング処理
$scr = new Scraping();
$result_array = $scr->scraping_data($tidy->body());
$result_date = $scr->scraping_date_data($tidy->body());
// UPDATEする
$tap = new TwitterAPI();
foreach( $result_array as $area_r ){
$status = "[".$area_r["center"]."::水道凍結情報] 今夜は【".$area_r["sfont1"]."】です。明日は【".$area_r["sfont2"]."】です。(".$result_date.")".INIT_URL;
if( $result = !$tap->update( $status , ID , PASS ) ){
echo"APIエラー";
} else {
echo $result ." " ;
echo $status ;
}
}
?>
はじめて作ったTwitterのBOT、感慨深いです。
一年ぶりにソースを見直したら、改善できそうなところが・・・・
そこに成長を感じたりもします。
Tidy関数を使ったスクレイピングとTwitterAPI(1)-Tidy関数でスクレイピング
2月 24th
APIとして使いやすい(動くサービスが作りやすい)TwitterのAPIを使って
昨年から少しずつ、Twitter-bot(自動投稿プログラム)を作成していました。
せっかくなので、ここにまとめておきたいと思います。
札幌市水道凍結情報配信bot
「水道凍結情報」-さっぽろお天気ネット-で公開されている情報を
WEBページから取得し、毎晩Twitterに配信するというbotです。
一定期間、一定地域にしかニーズはありません。
が、これも勉強です。
スクレイピング処理
今回スクレイピングしたかったHTMLはこんなソースです。
<tr>
<td>
<p class=center>中央区</p>
</td>
<td>
<img src="../images/freeze3.gif" alt="注意">
<span class=sfont>注意</span>
</td>
<td>
<img src="../images/freeze2.gif" alt="念のため">
<span class=sfont>念のため</span>
</td>
</tr>
ここから class=center と class=sfont の中身を3つセットで取り出したい。
取り出すために必要になる処理は大雑把にいうと以下となります。
◇ html情報を文字列として取り出す
◇ Tidy関数を使用し、文字列にストアされたドキュメントをパースする
◇ パースしたTidyオブジェクトを操作して、html情報を切り出す
◇html情報を文字列として取り出す
fopen関数を使って指定URLの情報を取得します。
//GetHttpSource
class GetHttpSource {
public function get_html($url){
if (($fp = fopen($url, "r")) == FALSE) {
$this->status = "エラーが発生しました。";
return;
}
$str = fgets($fp);
while (! feof($fp)) {
$str = $str . fgets($fp);
}
fclose($fp);
$data = mb_convert_encoding($str,"utf-8","auto");
return $data;
}
}
[プロキシを介してインターネットに接続している場合]
fsockopenを使います。
$proxy_name = 'your_proxy';
$proxy_port = 8888;
$proxy_cont = '';
$proxy_fp = fsockopen($proxy_name, $proxy_port);
if ( $proxy_fp == false ) {
echo "Error!!";
return false;
}
// プロキシ情報を付加してファイル取得
fputs($proxy_fp, "GET $proxy_url HTTP/1.0\r\nHost: $proxy_name\r\n\r\n");
while(!feof($proxy_fp)) {$proxy_cont .= fread($proxy_fp,4096);}
fclose($proxy_fp);
// プロキシ情報を除去してデータとして格納する
$data = mb_convert_encoding(substr($proxy_cont, strpos($proxy_cont,"\r\n\r\n")+4),"utf-8","auto");
return $proxy_cont;
◇Tidy関数を使用し、文字列にストアされたドキュメントをパースする
PHP: Tidy – Manualを参考に。
Tidy 関数 parseString() と cleanRepair() を使用します。
// HTMLの取得
$ca = new GetHttpSource(); // 自作クラス
$data = $ca->get_html(INIT_URL);
// Tidy関数config
$config = array('indent' => TRUE,
'output-xhtml' => TRUE,
'wrap' => 200);
// 取得したHTMLファイルの内容をUTF-8の文字コードで$configで指定した設定で、tidyオブジェクトを作成
$tidy->parseString($data, $config, 'UTF8');
// パースされたマークアップに設定に基く誤りの修正を行う
$tidy->cleanRepair();
◇パースしたTidyオブジェクトを操作して、html情報を切り出す
ノードとして保持されているTidyオブジェクト内の情報を、再帰的に読み込み、
必要な情報だけを取り出します。
当日(当夜)予想と翌日(日中)予想を判断するのは「読み込まれる順番」しかなかったので、
class変数に取得した値を突っ込んでいって全てが埋まったらarrayに格納という手段をとっています。
class Scraping {
private $center= "";
private $sfont1 = "";
private $sfont2 = "";
private $result = array();
private $result_date = "";
// ############ 各地区の予測を取得
public function scraping_data(tidyNode $node) {
$this->doScraping($node);
return $this->result;
}
private function doScraping(tidyNode $node) {
if(isset($node->id)) {
// $node->id が TABLEタグだったらそのなかにデータあり
if($node->id == TIDY_TAG_TD ) {
$chileNodes = $node->child ;
foreach( $chileNodes as $item ){
if(isset($item->id) && $item->id == TIDY_TAG_P ) {
if (isset($item->attribute['class'])){
// class属性値がcenterだったら
if (stristr ($item->attribute['class'] ,"center") !==FALSE){
// 区
$this->center = trim( $item->child[0]->value);
}
}
}
if( isset($item->id) && $item->id == TIDY_TAG_SPAN ) {
if (isset($item->attribute['class'])){
// class属性値がsfontだったら
if (stristr ($item->attribute['class'] ,"sfont") !==FALSE){
if( $this->sfont1 == "" ){
// 夜から朝方の予想
$this->sfont1 = trim( $item->child[0]->value );
} else {
// 日中の予想
$this->sfont2 = trim( $item->child[0]->value );
}
}
}
}
}
// 情報セットが取れたらリストに格納
if( strlen($this->center) && strlen($this->sfont1) && strlen($this->sfont2) ) {
$this->result[] = array("center" => $this->center ,"sfont1" =>$this->sfont1 , "sfont2" =>$this->sfont2 );
//echo "OK";
// クリアする
$this->center = "";
$this->sfont1 = "";
$this->sfont2 = "";
}
}
}
// 子供のノードが存在すれば、再帰的にdoScraping()を繰り返す
if($node->hasChildren()) {
foreach($node->child as $c) {
$this->doScraping($c);
}
}
}
}
おまけ(日付の取得)
このソースから日付も取り出すことにしました。
<h2>札幌市内の水道凍結予報</h2> <h3>各区の水道凍結指数</h3> <p class=comment>2009年02月24日 16時00分発表</p> <p class=clear></p>
スクレイピング処理はこんな感じになります。
class Scraping {
// ############ 日付を取得する
public function scraping_date_data(tidyNode $node) {
return $this->getDate($node);
}
private function getDate(tidyNode $node) {
if(isset($node->id)) {
// $node->id が Pタグだったらそのなかに日付データあり
if($node->id == TIDY_TAG_P ) {
// $node->id の class属性値がcenterだったら
if (stristr ($node->attribute['class'] ,"comment") !==FALSE){
return trim( $node->child[0]->value );
}
}
}
// 子供のノードが存在すれば、再帰的にdoScraping()を繰り返す
if($node->hasChildren()) {
foreach($node->child as $c) {
$result = $this->getDate($c);
if(strlen($result)) return $result;
}
}
}
}
これでスクレイピング処理が完了。
これをTwitterAPIを使用して、投稿します。
長くなったので続きは次回。
Frog.jsを使う。(1) – JavaScript Slideshow
11月 22nd
JavaScriptを使ったスライドショーです。
サムネイルをクリックすると、中心に画像が表示されます。
動きがキレイで、さらに実装が簡単なので、使いどころがありそうです。
[ダウンロードと設定]
FrogJS Javascript Gallery
から、FrogJS v1.1をダウンロードします。
内部には必要なスクリプト・サンプルが同胞されています。
prototype.jsとscriptaculous.js(effects.js)が必要ですが、
既にサイトに組み込んでいる場合は、frog.jsだけを設定すればOKです。
prototype.js→scriptaculous.js→frog.jsの順に読み込まれるようにしておきます。
<script type="text/javascript" src="xxxx/prototype.js"></script>
<script type="text/javascript" src="xxxx/scriptaculous.js?load=effects"></script>
<script type="text/javascript" src="xxxx/reflection.js"></script>
[実装方法]
このスライドショーには、表示用画像とサムネイル用画像のセット画像が必要です。
画像を準備したら、次のようにhtmlを記述します。
*画像を囲むブロック要素にid[FrogJS]を設定します。
*リンク要素のパス(href)に表示したい画像のURLを設定します。
*リンク要素のtitleに設定した値は、画像表示時のタイトルとして表示されます。
*リンク要素のrelにURLを設定すると、リンク画像にすることができます。
*img要素のsrcにサムネイル画像のURLを設定します。
*img要素のaltに設定した値は、画像表示時に説明文として表示されます。
<div id="FrogJS">
<a href="[画像URL]" title="[画像タイトル]" rel="[画像をリンクさせる場合はそのURL]">
<img src="[サムネイルURL]" alt="[画像の下に表示される説明文]" />
</a>
<a href="[画像URL]" title="[画像タイトル]" rel="[画像をリンクさせる場合はそのURL]">
<img src="[サムネイルURL]" alt="[画像の下に表示される説明文]" />
</a>
<a href="[画像URL]" title="[画像タイトル]" rel="[画像をリンクさせる場合はそのURL]">
<img src="[サムネイルURL]" alt="[画像の下に表示される説明文]" />
</a>
[以下略]
</div>
[CSS設定]
次のようなidのブロック要素が生成されますので、
お好みに合わせてスタイルを設定してください。
#FrogJS:画像表示部全体
#FrogJSCredit:aタグに指定したtitle表示部
#FrogJSCaption:imgタグに指定したalt表示部
[サンプル]
今回は秋に撮影した紅葉の写真を使用します。
(画像の読み込みが若干重いので動きがスムーズでないです・・・。)
次は若干のカスタマイズ方法と、APIを使って自動的に呼び出した画像を
スライドショーで表示させるプログラムに挑戦してみたいと思います。