まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2016/10

主にブログなどを使ったアマゾンアフィリエイトを行う場合、アマゾンアソシエイトのアカウントが必要です。またアフィリエイトリンクを動的に(プログラムで)生成しようとすると、これらに加えて「アソシエイトID」と呼ばれる ID を取得する必要があります。以下にそれらを取得する手順を紹介しますが、アマゾンのアカウント自体は取得済みであるとします。


まずはAmazon アソシエイトページに移動し、「無料アカウント作成」と書かれたボタンをクリックします:
https://affiliate.amazon.co.jp/

2016102801


自分のアマゾン ID とパスワードを指定してサインインします:
2016102901


次にアカウント情報を指定します。個人か法人かなどの質問がありますが、迷うような箇所は特にないと思われます。一通り入力後に「次:Web サイト情報」へ移動します:
2016102902


ここからが肝になる部分です。まずはアソシエイト ID と呼ばれるユニークな ID を指定します(他の人が既に使っている文字列は指定できません)。なお、このアソシエイト ID に "-22" を付加したものが正式なアソシエイト ID になります。

次にアフィリエイトを行うことになるブログや SNS などのウェブサイトの URL を指定します。ここで指定されたサイトのリンクから送客を行ったものがアフィリエイト対象となります。そして、そのウェブサイトの説明や紹介予定の商品についての説明を記載します:
2016102903


その下ではウェブサイトのカテゴリをおおまかに指定します。また収益化のために使っている方法についてを指定します。
2016102904


そのままアンケートに答えていきます。最後に契約条件を確認したかどうかのチェックボックスを ON にします:
2016102905


そしてボックス内に表示されているものと同じ文字列を入力して「完了」ボタンをクリックします。これで申し込みは完了です:
2016102906


数時間から数日後、ここで指定した内容に関する審査結果がメールで届きます。審査が通った場合はアソシエイト ID(自分が指定したもの + "-22")も記載されているはずです:
2016103001



これでアマゾンのアソシエイト ID を取得できました。

最近はスマホのカメラの性能が上がり、その結果として画像ファイルサイズが大きくなりました。解像度が上がったことは嬉しいのですが、画像ファイルをウェブアプリなどにアップロードしようとする際に時間がかかったり、パケット通信量が増えてしまったりします。また送信先の API やシステム設定によってはアップロードサイズに上限が設定されていて、そのままでは送れなかったりすることもあります。

そんな場合に「画像を小さくしてから処理する」方法が考えられますが、Java によるその一例を紹介します。画像ファイルデータが img 変数にバイト配列で格納できているという前提で、横幅を 800 ピクセル、縦横比を変えずに高さをそれにあわせる形で変更するような処理内容を記述しています(こうすると、大抵 1MB 以下になります):

byte[] img = null; //. この変数に画像データが byte 配列で格納されているものとする
   :
   :

//. 画像のサイズを変更
if( img != null ){
  BufferedImage src = null;
  BufferedImage dst = null;
  AffineTransformOp xform = null;

  InputStream is = new ByteArrayInputStream( img );
  src = ImageIO.read( is );

  int width = src.getWidth();    //. オリジナル画像の幅
  int height = src.getHeight();  //. オリジナル画像の高さ

  int w = 800; //. 幅をこの数値に合わせて調整する

int new_height = w * height / width; int new_width = w;
//. 画像変換 xform = new AffineTransformOp( AffineTransform.getScaleInstance( ( double )new_width / width, ( double )new_height / height ), AffineTransformOp.TYPE_BILINEAR ); dst = new BufferedImage( new_width, new_height, src.getType() ); xform.filter( src, dst );
//. 変換後のバイナリイメージを byte 配列に再格納 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write( dst, "jpeg", baos ); img = baos.toByteArray(); }

上記のように ImageIO と java.awt のアフィン変換を使って画像サイズに変換をかけて実装しています。

 

IBM Domino 上で Java サーブレットを動かすまでの設定と実装を紹介します。なお、今回は Domino バージョン 9 上で実行することを想定しています。


まずは Domino 上で Java サーブレットが動作するよう設定します。Domino 導入後、管理クライアントを開きます(ウェブ版でも構いません。その場合は webadmin.nsf を開きます)。サーバー設定文書を編集モードで開いて、"Internet Protocol" - "Domino Web Engine" - "Java servlet support" を "Domino Servlet Manager" に設定します。また必要に応じて Servlet URL path(デフォルトで /servlet)や Class path(同 domino/servlet)を編集して保存します。これで Domino サーバー側の設定は完了です:
2016102201


次に Java サーブレットを実装します。Domino 上の Java サーブレットは war ファイルを読み込む、といったことができないので、JDK でサーブレットの class ファイルを用意します。この辺り詳しくは以下のエントリも参照ください:
Eclipse(IDE) を使わずにJavaサーブレットを作る



上記エントリで紹介した HelloWorld サーブレットを動かしてもいいのですが、せっかくなので Domino のクラスを使って情報を取得して表示する内容にします。以下のソースコードを DominoInfo.java というファイル名で、UTF-8 で記述して保存します(青字部分で Domino クラスを使っています):
//. DominoInfo.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import lotus.domino.*;

public class DominoInfo extends HttpServlet{
  @Override
  protected void doGet( HttpServletRequest req, HttpServletResponse res ){
    try{
      NotesThread.sinitThread();
      Session session = NotesFactory.createSessionWithFullAccess();
      String username = session.getUserName();
      String version = session.getNotesVersion();
      
      res.setContentType( "text/plain; charset=UTF-8" );
      res.getWriter().println( username + " : " + version );
    }catch( Exception e ){
      e.printStackTrace();
    }finally{
      NotesThread.stermThread();
    }
  }
}

↑このサーブレットを実行するユーザー(つまりサーバー ID のユーザー)名と、実行中の Domino のバージョンを取得して表示する、という内容です。

これを javac でコンパイルします。このエントリでも紹介しましたが、javac のパラメータで UTF-8 エンコードであることと、ServletAPI.jar のクラスパスを指定する必要がありますが、更に加えて以下の2点を指定してコンパイルします:

(1) lotus.domino.* クラスを利用するので、Notes.jar のクラスパスを指定(以下のコマンド例では C:\IBM\Notes\jvm\lib\ext\Notes.jar に存在していると仮定)
(2) Domino 9 で使われている Java のバージョンは 6 (つまり JDK 1.6)なので、このバージョン互換でコンパイルするよう指定


具体的には以下のようなコマンドを実行します:
C:\tmp>javac -classpath c:\arc\servlet-api-3.1.jar;c:\IBM\Notes\jvm\lib\ext\Notes.jar -encoding utf-8 -source 1.6 -target 1.6 DominoInfo.java

コンパイルに成功すると DominoInfo.class というファイルが出来上がります。これを上記で設定した Domino の classpath(上の例だと domino/servlet)フォルダにコピーして、Domino を再起動します。

再起動後、ウェブブラウザで Domino サーバーの /servlet/DominoInfo にアクセスしてサーブレットを実行し、以下のような結果になることを確認します:
2016102202


↑サーバーを実行しているユーザーの名前(CN=Domino/O=Dot123)と、Domino サーバーのバージョン(Build V90_C06_12072012|December 07, 2012)が表示できました。 本来、これらの情報は Notes クライアントなどの特殊環境からでないと取得できないものですが、そのロジックを Java サーブレットという仕様でラッピングすることで HTTP アクセスで取得することができるようになったことが分かります。Notes/Domino データベースへの読み書きを実現するための1つの方法として使えます。

僕は普段 Java の IDE (統合開発環境)として Eclipse を使ってますが、久しぶりに IDE なしでサーブレットを作る機会がありました。結構ハマったこともあって、普段いかに Eclipse に頼っているのかを痛感しました・・・


今回やりたかったことは、ごくごくシンプルな話で、
 (1) サーブレットの Java ソースファイルを書いて、
 (2) JDK でビルドしてサーブレットの class ファイルを作る
という2つです。要はサーブレットの実行環境が特殊で war ファイルとかをデプロイできないため、 class ファイルをそのまま配置する必要がある、という(IBM Domino とかの)ケースを想定しています。

なお環境は Windows7 で、JDK 1.8 はダウンロード&インストール済みとします。



まず (1) のソースファイルは以下のような感じにしました。パッケージも指定していません。これを UTF-8 で記述して、HelloWorld.java というファイル名で保存します:
//. HelloWorld.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet{
  @Override
  protected void doGet( HttpServletRequest req, HttpServletResponse res ){
    try{
      res.setContentType( "text/plain; charset=UTF-8" );
      res.getWriter().println( "ハローワールド!" );
    }catch( Exception e ){
      e.printStackTrace();
    }
  }
}

↑特別なことは何もしてない、ごくごくシンプルな「ハローワールド!」です。


さて、これを (2) JDK でビルドして class ファイルに・・・という段階で何度かつまづきました。

まずは普通にそのままコンパイルしてみると、
C:\tmp>javac HelloWorld.java

HelloWorld.java:11: エラー: この文字は、エンコーディングMS932にマップできません
      res.getWriter().println( "繝上Ο繝シ繝ッ繝シ繝ォ繝会シ?" );

「ハローワールド!」という日本語文字列のエンコードでエラーが出てしまいました。ちゃんとソースコードは UTF-8 にしたつもりだったのに・・・

実は Windows 版の JDK の javac はソースコードが Shift-JIS で書かれているものとして動作するらしいのでした(なんちゅうクソ仕様・・・)。便利とは思えない仕様ですが、このエラーを回避するにはコマンドラインパラメータでソースコードのエンコードを指定必要があるのでした。

というわけで今度は UTF-8 を指定してコンパイル。すると別のエラーが大量に・・・
C:\tmp>javac -encoding utf-8 HelloWorld.java

HelloWorld.java:3: エラー: パッケージjavax.servletは存在しません
import javax.servlet.*;
^
HelloWorld.java:4: エラー: パッケージjavax.servlet.httpは存在しません
import javax.servlet.http.*;
^
HelloWorld.java:6: エラー: シンボルを見つけられません
public class DominoInfo extends HttpServlet{
                                ^
  シンボル: クラス HttpServlet
HelloWorld.java:8: エラー: シンボルを見つけられません
  protected void doGet( HttpServletRequest req, HttpServletResponse res ){
                        ^
  シンボル:   クラス HttpServletRequest
  :
  :

これら全てサーブレットの JAR ライブラリを(指定していないので)見つけられないことが原因のエラーです。これは普段の Eclipse 環境でも指定していることでしたが、すっかり忘れていました。

Tomcat あたりをダウンロード&インストールして、サーブレット JAR ファイル(servlet-api.jar とかいう名前だと思います)を見つけて取り出します(以下の例では c:\arc\servlet-api-3.1.jar というパスで保存しているものとして記述しています)。で、改めてこのファイルを classpath に指定してコンパイル:
C:\tmp>javac -classpath c:\arc\servlet-api-3.1.jar -encoding utf-8 HelloWorld.java
C:\tmp>

無事コンパイルできました。HelloWorld.class が出来ているはずです:
2016102201


・・・こ、こんなに難しかったっけ? (^^;


この応用で、作ったサーブレットを実際に IBM Domino 上で動かすための設定についてはこちら


UNIX コマンドの1つである screen はターミナル内で使えるとても便利なコマンドです。僕も時間のかかるバッチ処理などを行う際に、
 (1) まず screen
 (2) screen セッションの中でバッチ開始
 (3) セッションをデタッチ(Ctrl A + Ctrl D)
とすると、(3) の後にログアウトしてもバックグラウンドでバッチ処理を実行し続けてくれ、再ログイン後に screen -r コマンドを実行することで再度その処理を実行していたセッションに再接続(アタッチ)することができるのです:
$ screen -r

そこで処理が終わっていれば結果も確認できるし、まだ終わっていないようであれば、再度デタッチしてログアウトして・・・とても重宝しています。

さて、上記のように明示的にデタッチしてからログアウトする場合はよいのですが、通信状態がよくない場合などに
 (1) まず screen
 (2) screen セッションの中でバッチ開始
 (3) セッションをデタッチする前に通信が切れるなどして強制終了
してしまうことがあります。こうなるとそのセッションは実行中のままにはなりますが、再度ログインしてから再接続することはできなくなってしまうのです:
$ screen -r
There is a screen on:
        nnnn.pts-0.xxxxx   (Attached) 
There is no screen to be resumed. エラーメッセージが出てアタッチできない
$

そんな不可抗力な現象に遭遇した場合、以下の方法で再接続することができます。

まずは現在実行中の screen プロセスを確認します:
$ ps aux|grep pts|grep sshd
kkimura   1111  0.0  0.2   ****  **** ?        S    18:42   0:00 sshd: kkimura@pts/0
kkimura   2222  0.0  0.2   ****  **** ?        S    20:47   0:00 sshd: kkimura@pts/9
kkimura   3333  0.0  0.0   ****   *** pts/9    S+   20:49   0:00 grep -r sshd

↑一番下のプロセスは grep なので関係なし、その上の pts/9 は現在のセッション。一番上の pts/0 のセッションが残ってしまっているためにアタッチできなくなっています。

というわけで pts/0 のセッション(PID が 1111 のもの)を kill します:
$ kill 1111

これで再度アタッチできるようになります:
$ screen -r


このページのトップヘ