Logical Rabbit.

さくらのVPS

JavaScript

簡易なPageView測定器の実装

長期にログを取る用途ではなく、開発過程でどのページがどのタイミングでアクセスされたかをチェックするための簡易サーバとクライアントサイドからの呼び出しコードです。

簡易ですがサーバ側でログを取るので、Webブラウザの環境に依存しにくい動作を期待できます。次のようなケースのとき向け。

  • JavaScriptコンソールやWebブラウザの通信ログでは余計な情報が多すぎる
  • モバイル端末での挙動確認なのでWebブラウザの開発者ツール類が使用できない

起動させたサーバに対して ?view=<測定対象URL> のようにしてGETアクセスすると、 <測定対象URL> のみを console.log() で出力します。後はそのまま眺めるなり、もう一工夫してファイルに記録するなり、用途に応じてカスタマイズ。

サーバ側

node.js 実装 + Docker 起動。停止時は適宜 docker stop などで。

index.js:

const express = require('express')

const server = express()
server.get('/', (req, res) => {
  const url = new URL(req.url, `${req.protocol}://${req.hostname}`)
  console.log('pageview: ' + url.searchParams.get('view'))
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.statusCode = 200
  res.end()
})

server.listen(8080)

起動:

$ docker run -it -v${PWD}:/mnt/app -p 8080:8080  node node /mnt/app/index.js

クライアント側(Webブラウザ側)

jQueryやaxios等が使えるのであれば、そちらの方が色々便利かと思います。今回はターゲットWebブラウザがChromeだけなので、XMLHttpRequest決め打ちでよいだろうと判断。 なお、例ではRuby on Railsへ組み込んでいるので turbolinks:load イベントからの起動になっています。

<script>
  document.addEventListener('turbolinks:load', event => {
    const url = event.data.url;
    const req = new XMLHttpRequest()
    req.open('GET', 'http://' + window.location.hostname + ':8080/?view=' + url);
    req.send();
  });
</script>

gulp-gh-pagesを使おうとしてハマったメモ(おそらく今だけの一過性)。

発端

gulp-gh-pages を使おうとしたら、以下のような謎のエラー。

node_modules/gulp-gh-pages/node_modules/gift/lib/commit.js:145
      ref1 = /^.+? (.*) (\d+) .*$/.exec(line), m = ref1[0], actor = ref1[1], epoch = ref1[2];
                                                       ^

TypeError: Cannot read property '0' of null

エラー箇所のコードを読むと、gitのcommitログ解析でおかしくなっているように見えるのだけど、なんでcommitログの正規表現処理なんてやっているのかが不明だにゃー、と。

原因

ググると以下のページが見つかったので、取り敢えず gulp-gh-pages-will ではなくて手動で何とかする方法をとってみた。

gulpの「gulp-gh-pages」がエラー履くようになったときの対応

記事内の解説、およびgiftのissueとかを読んでみると、giftの過去バージョンに起因する問題で、gift自体では問題解決済みの版が出ている。しかし、gulp-gh-pagesのpackage.jsonが古いままなので、gulp-gh-pagesとしては問題解決できていない、ということらしい。

どうも gulp-gh-pages 本家もGitHub上では次バージョンのタグが出ていて、最新のpackage.jsonではgiftも最新版使うようになっているので、近々根本的な解決があることを期待したいところ。

なお、gulp-gh-pagesをGitHub版で使えばいいよね、と思ってやってみたところ、今度は gulp-gh-pages が依存する gulp に未リリース・GitHub版を要求され、仕方ないので gulp もGitHub版にしてみたところ、新たな謎エラーが出てしまったというオチがつきましたとさ。

解決手順

  1. "gift": "^0.10.2" を package.json に追記
  2. npm install 実行後、rm -rf node_modules/gulp-gh-pages/node_modules/gift として gulp-gh-pagesのインストール結果内giftを削除(こうしないと v0.10.2 のほうを使ってくれない)

Amazon Product Advertising APIについての備忘録。

JavaScriptベースで一通りつついてみたので、実装方法の備忘録。秘密鍵扱うので、クライアントサイドJavaScriptはNGですな。基本的にはプログラミングガイド読めば分かるものです。コーディングはお仕事しますよ? 的な。

必要なもの

アクセスキーID:
Amazonアソシエイト・セントラル →ツール →Product Advertising API で取得可能
秘密キー:
Amazonアソシエイト・セントラル →ツール →Product Advertising API で取得可能(認証情報生成時のみ表示されるようなので注意)
アソシエイトID:
Amazonアソシエイト・セントラルの右肩に出てるアレです

実装上のポイント

一先ず、ASINで指定した特定商品の情報取得を試しました。

  • エンドポイントは固定。シグネチャ生成時にhostとpathに分割することになります。
  • クエリは文字コード順でソートすることになるので、& で連結せずに扱ったほうが良さ気でした。ソートはArray.sort()のアルゴリズムで問題ない筈。
  • 各所の値はencodeURIComponent()でエンコード。
  • TimestampはISO形式の日付+時間ですが、ミリ秒以下があるとエラーになりますので(new Date()).toISOString()は使用できません。
  • シグネチャはbase64 エンコードの HMAC_SHA256です。Node.jsの場合 crypto.createHmac()が使用可能です。

うっかりハマる点として、各所に出てくるサンプルでのエンドポイントがアメリカ版ですが、日本のAmazonでアカウント登録している場合は日本のエンドポイントを使う必要があります。

処理の流れ

  1. タイムスタンプを生成します。UTC~メソッド群を使用して、ミリ秒を含まないISO形式の日付を生成。getUTCMonth()が0~11を返すことと、1桁の値は右側に0が必要になることに注意
  2. クエリを項目ごとに分割・格納したArrayを組み立てます →query

    AWSAccessKeyId
    アクセスキーID
    AssociateTag
    アソシエイトID
    Operation
    ItemLookup (今回は商品情報取得なので)
    ItemId
    ASIN
    Timestamp
    タイムスタンプ
  3. エンドポイントURLをバラします。Node.jsの場合url.parse()でパースして、hostとpathを使用します。
  4. クエリをソートし、&で連結しておきます。 →sorted_query
  5. 'GET', エンドポイントのhost, エンドポイントのpath, sorted_queryを\nで連結し、crypto.createHmac()で処理した後digest('base64')でBASE64形式で取り出し、更にencodeURIComponent()でエンコードします。 →signature
  6. エンドポイント + ? + sorted_query(queryでもいい筈) + & + signature にアクセスします。後はレスポンスを適宜調理。

リクエスト制限は「1秒に1回」とされているのですが、試行しているときにコード修正で明らかに数秒以上経過してても制限にかかってエラーになったので、もう少し余裕持ったほうが良さそう。

ところでPAAPIってやっぱり「パァイ」って読めばいいんですかね。

Vue.js+&lt;input type=&quot;date&quot;&gt;+初期値new Date()の場合

data() で初期値を定めるとき、new Date()の値をyyyy-mm-ddにしてやれば万事解決。←結論

細かく解説すると。

  • Vue.jsではコンポーネントの data() で初期値セットを返す
  • <input type="date" > はvalueとしてISO形式(yyyy-mm-dd)を与えないと初期状態として日付を表示してくれない(値があっても "yyyy/mm/dd" というユーザに大変お優しい表示となってしまう)
  • <input type="date" > に new Date() の値をそのままセットしても上記理由によりまともに表示してくれない

解決パターン。

Google Shortener(Google短縮URL) API。

Google Shortener APIで短縮URLを生成する処理をちょっと試してみたのでメモ的に残しておく。

  • APIキーは各自Googleアカウントで発行すること
  • fieldsクエリは全部乗せになってます
  • 正常に実行できると、アラートダイアログで結果が表示されます
  • エラー処理? なにそれおいしい?
$(function() {
  var apikey = <各自APIキーを入力>;
  var url = 'https://twitter.com/mami_tuchino'; /* これが短縮したいURL */
  $.post({
    url: 'https://www.googleapis.com/urlshortener/v1/url?fields=analytics%2Ccreated%2Cid%2Ckind%2ClongUrl%2Cstatus&key=' + apikey,
    data: JSON.stringify({ longUrl: url }),
    dataType: 'JSON',
    contentType: 'application/json',
    success: function(json) { alert(json.id); },
    error: function(error) { console.logt(error); }
  });
});
 

Google Map JavaScript APIで任意のマーカーアイコンを使用する方法と、そのアイコンの凡例をHTML表示するメモ。

1) ペイントソフトで1枚の画像ファイルに複数のアイコンを描いて 2) Google Map JavaScript APIで画像ファイルから各々のアイコンを切り出し、マーカーアイコンとして適用して 3) 同時にHTMLでも各々のアイコンを切り出し、凡例を表示する までの手順メモです。

取り敢えずメモなので説明画像も何もないですが、そのうち追加すると思いますよ、えぇそのうち…

用意するもの

  1. お絵かきソフト (アンチエイリアシング、透過PNG画像作成ができるもの)
  2. アイコンデザイン画
  3. HTMLとJavaScriptソースを書くためのツール
  4. Google Map JavaScript APIのAPIキー

手順1. 画像ファイルの作成

アイコンをまとめた画像ファイルの作成については、古のスプライトとかビットマップ画像とかを見たことがある人なら何の説明も要らない気がします。要はアレです。

何はともあれ、画像ファイルにアイコン画像を描きこみます。この際、アイコンの形状に関わらず、四角形(正方形のほうが管理しやすいと思います)の中央に収まるようにします。

次に、アイコン形状的に余白となる部分をマジックワンド等で選択、透過部分となるように設定します。具体的には消去して透明にするとか、アルファチャネルいじるとか。

透過部分の指定が終わったら、透過PNG画像として保存します。ここでは marker_icons.png とします。たぶんGIFでもJPEGでも、透過できて一般的なWebブラウザで表示できる形式なら何でもOKと思います。まあJPEGは予想外のゴミが入りそうだから止めた方がいいと思うけど。

手順2. Google Mapで使用するマーカーへの適用

マーカー( google.maps.Markerクラス )へ任意の画像ファイルを指定する方法は、コンストラクタのmarkerOptionsに iconプロパティ を指定することで行います。iconプロパティへはSVGパスかオブジェクト( google.maps.Icon )を指定可能なので、今回は後者のオブジェクト指定を使用します。

iconオブジェクトでは画像ファイルのURL (url)、画像ファイル中でのアイコン開始位置 (origin)、アイコンの大きさ (size) を指定します。他にもアイコンに被せるラベル文字列の開始位置などを指定できるので、微調整が必要な場合はこれらで調整していきます。

origin プロパティは google.maps.Pointクラス、size プロパティは google.maps.Sizeクラスを指定します。

例: /images/makrer_icons.png を読み込み、画像左上から30×30ピクセルのアイコン画像を使用する場合

var pos = new google.maps.LatLng( lat, lng );
var markerOptions = {
  label: 'marker',
  position: pos,
  map:       window.map,
  icon: {
    url: '/images/marker_icons.png',
    origin: new google.maps.Point( 0, 0 ),
    size: new google.maps.Size( 30, 30 )
  }
};
var marker = new google.maps.Marker( markerOptions );

手順3. HTMLによる凡例の表示

凡例を表示する際、先述の makrer_icons.png をそのまま表示してしまうと、複数のアイコン画像がずらずら並んだモノが出てしまいよろしくありません。このファイル自体に凡例としての説明部を書き込んでしまうという手もありそうですが、なんだか無駄が多いので止めておきましょう。うん、止めよう(ちょっと企んだ)

凡例の構成としては 1) アイコン、 2) 説明文 の列挙となりますので、 <ul> タグを使用して、リストのヘッダにアイコン画像を表示することにします。この際 <li> タグの list-item-image 属性では画像のクリッピングができないので、:before を使用して背景画像として表示することにします。

例: /images/makrer_icons.png を読み込み、画像左上から30×30ピクセルのアイコン画像について凡例を表示する場合

<style>
.marker {
  list-style-type: none;  /* デフォルトのリスト接頭辞は非表示にする */
}

.marker:before {
  background: url('/images/marker_icons.png') no-repeat 0px 0px; /* 画像を読み込み、左上部分から表示 */
  width: 30px;   /* 画像の表示幅 = クリッピング領域 */
  height: 30px;  /* 画像の表示高さ = クリッピング領域 */
  top: 0;
  left: 0;
  display: inline-block; /* 幅を指定するのでblock、<li>の本文に並べるのでinline */
  vertical-align: middle; /* バランスを考えて<li>の本文が画像中央に来るようにする */
  content: ' ';
}
</style>

<ul>
  <li class="marker">これはアイコンの説明文です</li>
</ul>

実際には複数のアイコンを説明することになるはずなので、クラス定義はアイコンの数だけ用意し、表示位置を変えていくことで切り出し位置を定義します。

ICS形式のカレンダーデータをJavaScriptでいじるときのメモ。

…これ、結局「Ical」と「Webcal」って何がどう違うんでしょうね…。何度調べてもよく分からん。ともあれ、扱うデータは「*.ics」なので「ICS形式」と呼んでおけば間違いなかろう、と。

某所で提案しようと下調べしていたのだけど、概ねめどが経ったところで募集状況を見に行くと既に終わっていたいつものパターンでしたとさ。まあフルタイム職が終盤に差し掛かってちょいと忙しかったしね…。というところで調べた成果の記録と供養など。

実行環境と使用ライブラリ

実行環境としてはNW.jsを使用して、Node.jsで実装しています。NW.jsはJavaScriptとHTMLとCSSでお手軽に書けるうえ、JavaScriptなのでWebアクセス系は楽々扱え、しかも実行バイナリはWindows、Mac OS X、Linuxと幅広く対応できるのがとても良いと思います。

コンパイラ系言語を使わなくて良いというより、クロスプラットフォームが簡単に実現できる、モノによってはWebアプリケーション化まで視野に入れることができるという点が気に入っています。

でも最近Go言語も気になっているのだけど、あっちとの比較はどんなもんだろう…?

もとい。

使用ライブラリは icalパッケージ。加えて、画面制御系としてjQueryも使います。

実装

icalパッケージでWeb上のICS形式読み取りとオブジェクト化は容易に実現できます。

問題はICSから読み取ったイベント情報が「繰り返し」だった場合。

例えばGoogleカレンダーで「2016年1月1日から12月30日まで、毎週金曜日に「今日は金曜日!」とセットした場合、ICSデータには「毎週金曜日に「今日は金曜日!」と追加されるのではなく、開始が1月1日、終了が12月30日、かつ「週ごとの繰り返しで金曜日のみ有効」というルールが付与されたイベントが1件だけ追加されます。

このようなイベントをicalパッケージで処理した場合、rruleプロパティ内に繰り返し情報が格納されます。さらに rrule.all() とすると具体的な日付に展開したArrayが返ってきますので、これを使用することで他のイベントと同じように日付として扱うことが可能になります。

(以下のCALENDAR_URLで指定しているカレンダーは、非公開(ICSのURLを知っている人のみ参照可)として実際に置いてあります。

var ICal = require( 'ical' );

var CALENDAR_URL =
  'https://calendar.google.com/calendar/ical/u78uo80t8b784ojbbf265tpkv4%40group.calendar.google.com/private-8d07e17f65ddac761f8cf190f3e69fee/basic.ics';

ICal.fromURL( CALENDAR_URL, {}, function( error, data ) {
  if( error ) {
    console.log( error );
  }
  else {
    var keys = Object.keys( data ).sort();
    keys.forEach( function( key ) {
      var friday = data[key];
      if( friday.rrule ) {
        friday.rrule.all().forEach( function( realDay ) {
          console.log( [
            friday.summary, realDay.toLocaleDateString()
          ].join( ' ' ) );
        } );
      }
    } );
  }
} );

なお、rruleがあるデータの場合なぜかstart, endにはtoLocaleDateString()等が使用可能なデータが入っていませんので注意。

購入検討中の書籍メモ (2016.09)

…という名目のアフィリエイトリンク集。実際に買うか、いつ買うかは未定だけど気になっている本。全て電子版あり。実際に買ってレビューする気になったら別記事に分けると思われ。

ユーザーストーリーマッピング

実装技術というよりその1段上、ハンドリングとかディレクション系の本と理解。個人事業もしくは零細企業規模の職場でシステム開発をすると、どうしても発注主の意図を汲みつつ方向性をまとめたり調整したりする必要が発生するので、たぶんまとめて全体を把握した後、後々まで読み返すような使い方になるんじゃないかなー、と期待してます。

未購入(物理書店で軽く内容把握)、AmazonのほかオライリーのEbook Storeで販売中。

開眼!JavaScript

発行はかなり前なんだけどそういえば未読だったし、JavaScriptはどうしても「なんとなく」で使って覚えてしまっている部分が多いので、たまに基本を読み返すのに良いかな、と思った次第。ページ数も少なく、気になるところだけ流し読みするのにも良さそうだし。詳細は「JavaScript」とかをあたることにして、全体の復習はこちらで随時行う方向で。

未購入(物理書店で軽く内容把握)、AmazonのほかオライリーのEbook Storeで販売中。

現場でかならず使われているjQueryデザインのメソッド

個人事業やるようになって、やはりjQuery等でお手軽かつ見目良くWebサイト開発する需要は高いなぁと思うと同時に、自分自身のデザインバリエーションが少ないことを痛感してます。なにせ今まで「動けばよい」の世界というか「チェックマトリックスでテストできることが最優先のデザイン」の世界だったので「画面幅によってデザインが変わる」ような一般的な設計とは縁遠かったわけで(設計書どおりのデザイン配置にならないのはバグなので、画面幅を強制固定するのが正道、TABLEレイアウトこそ至高、の世界)。

デザイン(動き)さえ分かれば技術的には自分で考えて実装できると思うのですが、まあ使えるスペアタイヤがそこに転がっているのに使わない手も無いだろうと。

ということでサンプル集的な用途を期待。Webで色々見つけてくるのも、技術検証とか著作権確認とかが面倒になってきたから。

未購入(物理書店で軽く内容把握)、Amazon kindle版あり。

Google Apps Scriptで開発するときの備忘録。

最近クラウドワークスでのお仕事でいくつか Google Apps Script を使用したのだけど、なかなか使える良い子であると同時に癖もある奴なのでいつも使うパターンをメモするなど。

スクリプトの実行時間は1回あたり6分

仕様的には Quotas for Google Services を参照。

細かい処理をちょいちょいとこなす程度であれば気にする必要はないのだけど、大量のファイルとかを扱い始めるとハマる。というかハマッた。

対策としては「6分以内に終わらせる」もしくは「6分超えそうだったら一度中断し、改めて再開させる(中断・再開が可能なつくりにする)」の2パターン。ただこの制限値に引っかかるような処理をさせると結構動きがトロいのも分かるので、第3のパターンとして「Googleの外でやる(普通のサーバー上でGoogle APIを呼び出す)」を考えに入れておくべきなのかと思った。

処理を一度中断し、自動で再開させるのは Properties + Trigger 、ところにより JSON

いくつかググって、最終的には Google Apps Scriptで5分の壁(タイムアウト)を突破するを参考にしつつ自分なりのパターンを作ることにしました。

  • スクリプトの処理開始時刻を保存しておき、開始後何分経ったかを随時チェック

  • 開始後6分を超過する前に十分な余裕をもって中断処理に入る

  • スクリプトの処理に必要なデータは Properties Service に格納する

  • ただし Properties はString value しか扱えないので、構造化データなどを格納したい場合は JSONフォーマットに変換して格納する

    • Propertiesへ格納する際は JSON.stringify()
    • Propertiesから復帰する際はJSON.parse()

    なお当初Base64を使おうとしていたのは秘密だ(待て)

  • スクリプトを終了後自動的に再開させるのは Script ServiceClockTriggerBuilder クラス。

  • 前述のBlogでも言及されていたし実際うまくいかなかったのだけど、Triggerで「after(durationMilliseconds)」があるくせにセットしてもちゃんと発動しない。…というか発動するのかもしれないけど「(plus or minus 15 minutes)」などとのんびりしたことを言われてしまっては使うわけにはいかんがな。せめて単位をsecondsにしてくれ…。

    everyMinutes(n) で毎分起動→起動後に多重起動防止のためトリガーを削除 のパターンでいきませぅ。

    ただ、ここはむしろトリガー追加+削除を繰り返すより、別途キューを用意しておき、毎分キュー確認→なにかあれば実行、何もなければ休眠、のほうがスマートな予感。

参考資料など。まー私が使ってるのはオライリーが販売しているPDF版ですがねハハハ(これはこれでよりによって「String」の項にしおりが設定されていないという痛恨のバグがある)。