Amazonアソシエイトで売上がない場合Amazon Associates Link Builderは使用できない。

このブログのタイトルの元ネタになっているので、モンスーノのロックのAmazonアソシエイトリンクだけはどうしても設置したいのですが、iframeタイプの広告を選んだ場合、ir-jp.amazon-adsystem.comへのアクセスがタイムアウトしてbodyのレンダリングに時間がかかる問題が発生していました。ちなみにir-jp.amazon-adsystem.comの読み込みが遅いのはあまり問題になっていないのか、調べてみてもほとんど記事が見つかりませんでした。
参考:Amazonアソシエイトのリンクがとても重い - 熊茶壜 + 遊戯三昧
ir-jp.amazon-adsystem.comは1px*1pxの画像で読み込まれますが、用途がアクセス解析なので消して良いと思っています。

やってみたこと

Amazon Associates Link Builder – WordPress プラグイン | WordPress.orgでリンクを生成してir-jp.amazon-adsystem.comのimgタグだけ削除すればいいかと思い、導入してみました。

発生した問題

Amazon Associates Link Builderを使用してアソシエイトリンクを生成しようとした場合
You are submitting requests too quickly. Please retry your requests at a slower rate. For more information, see Efficiency Guidelines.で503エラーが発生しました。一回もAPI実行してないのにtoo quicklyが表示されたので変な笑いが出てしまった。

原因

以下の記事に答えが載っていました。ありがたいです。

売上実績がないとProduct Advertising APIが使用できないようです。別にProduct Advertising APIが使いたいわけではなく、ロックの販売ページまで誘導できればいいので、画像とリンクを普通に組み合わせたうえで、ir-jp.amazon-adsystem.comのimgタグを削除してウィジェットに設置しました。

ロック、今見るとマケプレ価格で59800円になってますが、おもちゃとしての出来はスティックのり以下なので、これ買うならスティックのり買ったほうがいいです。

2019/03/19 追記!!!!!!!!!!!!!

モンスーノの第1話がYoutubeで配信されました!!!この記事書いた次の日に!!!!運命!運命を感じる。(ジョーカーさんっぽく)
【公式】獣旋バトル モンスーノ 第1話「ロック!(鍵)」 - YouTube
傲慢かもしれないんですがリンクを張っておきます。すべての鍵にロック一つってかっこいいけどマジで意味わかんないですね。
CV:KENNで繰り出される主人公のハイセンスで容赦ない煽りとイカれた言動をぜひ堪能してほしいです。1話はそんな煽らないけど。

パルが更新されたら通知するLINE BotをJavaでつくる。

パルの更新をチェックするのが不毛なので、パルが更新されたら通知するLINE botを作りました。Feedlyとかだと見逃す。

使用した環境・ライブラリ

Javaを採用した理由は、持ってるマシン(Windows 10 2台(内1台はほぼすっぴん)、Raspberry Pi 3(raspbian stretch)、CentOS7)全部で動くようにしたかったからです。DockerはWindowsマシンの片割れで正常に動作せず、Windows Subsystem for Linuxはすっぴんのほうでダウンロードに失敗して環境構築がめんどくさくなってしまい、全員JDKなら入ってるからこれでいいやと思いました。環境ごとにOracleJDKかOpenJDKかがバラバラなのですが、大したことはしないので問題ないと判断しました。

全環境でコマンド一発で動くようにしたいので、依存するクラスをすべて内包した実行可能jarを作ることにします。

実際のコード

java

package biz.retrorocket.hai;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;

import com.linecorp.bot.client.LineMessagingClient;
import com.linecorp.bot.model.PushMessage;
import com.linecorp.bot.model.message.TextMessage;

import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.SyndFeedInput;

public class RSSReaderBot {

    private static final String CHANNEL_ACCCESS_TOKEN = "TOKEN";
    private static final LineMessagingClient CLIENT = LineMessagingClient.builder(CHANNEL_ACCCESS_TOKEN).build();
    private static final String URL = "http://negineesan.hatenablog.com/rss"; // パルのFeed
    private static final SyndFeedInput INPUT = new SyndFeedInput();
    private static final String CHANNEL_ID = "CHANNEL_ID"; // 通知を流したいチャンネルのID

    public static void main(String[] args) throws IllegalArgumentException, FeedException, IOException {

        long lastUpdateDate = 0;

        while (true) {
            SyndFeed feed = INPUT.build(new InputStreamReader(new URL(URL).openStream(), StandardCharsets.UTF_8));
            SyndEntry entry = feed.getEntries().get(0); // 最新のエントリだけ取得
            long publishedDate = entry.getPublishedDate().getTime();

            if (publishedDate > lastUpdateDate) { // 「最新の日付 > 現在時刻-更新間隔を引いた値」でも別に良い
                CLIENT.pushMessage(new PushMessage(CHANNEL_ID,
                        new TextMessage("パルが更新されました" + "\n" + entry.getTitle() + "\n" + entry.getLink())));
                lastUpdateDate = publishedDate;
            }
            try {
                Thread.sleep(10 * 60 * 1000); // 10分
            } catch (InterruptedException ignore) {
            }
        }
    }
}

今話題の無限ループで10分毎に更新をチェックします。alertじゃないから大丈夫なんじゃないですかね…。(震えながら)httpclientは不要です。

build.gradle

plugins {
    id 'java'
}

version = '0.1-SNAPSHOT'

dependencies {
    // LINE BOT SDK
    implementation 'com.linecorp.bot:line-bot-api-client:2.4.0'
    // ROME
    implementation 'com.rometools:rome:1.12.0'
    implementation 'org.slf4j:slf4j-simple:1.7.25'
    // test
    testImplementation 'junit:junit:4.12'
}

repositories {
    mavenCentral()
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

jar {
    manifest {
        attributes 'Main-Class':'biz.retrorocket.hai.RSSReaderBot'
    }
    from configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}

Gradle 3.4あたりからcompileじゃなくてimplementation使わないといけないらしいので以下を参考にimplementationで書きました。

LINE SDKとROMEがslf4j-apiに依存していて、実行時にSLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder"でWARNが出てくるので、slf4j-simpleも一緒に指定しています。

あと、ソースコードをUTF-8で書いている場合、build.gradleのオプションでエンコードを指定するのを忘れるとこうなります。

久々に力強い文字化けを見た。

さくらVPSのホストリプレースに伴いサービスが停止(2019/3/25 AM10:00~)します。

延期になってたさくらVPSの機材老朽化対応に伴うメンテナンスの案内が2月末に来ました。以下メンテナンス内容の抜粋です。

■ メンテナンス日時
2019年03月25日(月) 10時00分 – 2019年03月25日(月) 13時00分

※メンテナンス後、サーバ内でご利用中のサービスの起動状態
についてはお客様にてご確認をお願いいたします。

■ 作業概要
新規ホストサーバへデータ移行のためのシステム設定を行います。

■ メンテナンス作業詳細
メンテナンスは以下の流れで実施いたします。

——————————————————————
1. ご利用中VPSを停止状態に遷移 ※
2. ホストサーバのメンテナンスを実施
3. ご利用中VPSを起動

※前回のメンテナンスにて、一時停止状態への遷移中に想定しないホスト
サーバのダウンが発生いたしましため、お客様VPSを停止させて作業を
実施させていただきます。

※お客様VPSの停止時間は約2時間程度を予定しておりますが
想定時間よりも前後する場合がございます。
——————————————————————

このブログとworks内のサービスは全員同じVPSに同居してるので全員アクセスできなくなります。前使ってたVPSはメンテナンス後にIPアドレス変わってたり、メンテナンス後にVPSの設定が書き換わってオープンプロキシになってたりしたんですが、さくらさんのことはめちゃくちゃ信頼してるので大丈夫だと思います。

Rails 4アプリのCSRF tokenをクライアント側で生成できるか調べた。

タイトルにだいぶ語弊があるけど、的確な表現が思いつかなかった…。
Railsは全く縁がなくて基礎知識ゼロだったのですが、今後の自分の役に立ちそうだったので備忘録として残しておきます。

前提

あるRuby on Rails 4製のWebアプリがあり、ユーザは[page A]->[form B]の順に遷移します。
このWebアプリを作ったのは自分と全然関係ない人たちなので、ソースコードや設定値等を見ることはできませんし、詳細な仕様もわかりません。
form BへのリクエストをWebブラウザ以外のRESTクライアントで行いたいのですが、form Bへのリクエストにはpage Aで発行されたcsrf_tokenと、(このアプリはcookie sessionを採用しているため)cookieが必要です。

知りたいこと

一度page Aにアクセスしてしまえば、以降はcsrf_tokenとcookieを使い続けることで、form Bに直接リクエストを投げることができます。
ただ、今後似たような場面に遭遇した場合、できれば無駄なアクセスを無くしたいので「page Aに一度もアクセスせずにtokenを発行する方法はないのか?」が気になりました。

調べたこと

tokenは自分で発行できるか?

Rails セキュリティガイド - Rails ガイドや、RailsでのCSRF token 発行 / 検証のロジック - Qiitaを参照すると、「セッションに保存された_csrf_tokenの値」と「HTMLページから取得したcsrf-tokenの値」で検証を行って、問題がなければリクエストが通る仕組みのようです。
それなら、検証を通過するような値を自分で作ってcookieに書き込めば理屈としては問題ない気がします。果たしてそんなことはできるのか?

Railsのcookieの扱いはどうなっているのか?

page Aで発行されたcookieの中身を見てみると以下のような感じでした。

#LWP-Cookies-1.0
Set-Cookie3: アプリ名_session=dmJ3aTYvbG8yRE91K2Mxa3dRU016TWNNUnZicTVXSWFud2VSMzRIQVMrQ3V4enBtUkRPTHg3UnFnbFRGSGV4eWduVmgwdTEzRGRjZFJEOV(中略)klpbTREczJiSnI2UExPV2ZQZmpVTEM3UURKLS1CWWlsOVJLVnFEVkE0QndpanhOczlnPT0%3D--daa857c08b96dc69cf56bd4f12706fd3b7b2920c; path="/"; domain=xxx.xxx.xxx; path_spec; secure; discard; HttpOnly; version=0

Base64エンコードされてるっぽい値なので、「デコード後に値を書き換えてエンコードし直せばいけるのでは?」と思ったのですが、さすがにちゃんと対策されていました。ですよね。
以下のページに答えが載っていました。
Cookies on Rails | BigBinary Blog

session , by default, uses signed cookies which prevents any kind of tampering of data but the data is still visible to users.

値の中(--以降)にハッシュ値が存在していて、改ざんすると正確なハッシュ値が計算できないので、アプリがnilを返すんですね。以下のページもわかりやすかったです。
Rails で (Rack の) セッション情報を Cookie に保存する仕組み - Qiita

Rails 4 stores session data in encrypted format

加えて、Rails 4ではユーザがMarshal.loadで中身を覗けないようにしてありました。実際試しましたが、以下のようにエラーが発生しました。

convert.rb:13:in `load': incompatible marshal file format (can't be read) (TypeError)
        format version 4.8 required; 110.56 given

Nginx Helper 2.0.1でコメント時にパージが動かない。

Nginx Helperで個別記事のキャッシュをパージできない。 – return $lock;
これとは別の問題です。2.0.1の段階でコメントがついた時に個別記事のパージが動かなくなりました。前回とは異なり、個別記事のURLは正しいようです。

バージョンアップの度に動かなくなることが多いプラグインのため、以下から適当に1.9系のバージョンを持ってきたところ動作しました。やはり2.0.x系に問題がありそうです。修正されるまでバージョンアップはなしで運用かなと思います。
Releases · rtCamp/nginx-helper · GitHub

調べてみると同じ問題に当たっている方がいて、やはり1.9系に戻してるようです。
Nginx-Helper で上手いことキャッシュのパージが行われない問題 | ぶっちろぐ