読者です 読者をやめる 読者になる 読者になる

SpringBootでEmbeddedWebContainerを起動させない

qiita.com

AWS Lambda ファイル便(S3とzipでパスワード認証)

AWS Lambda

この記事は AWS Lambda アドベントカレンダー 20日目の記事です。
前回19日目は、 Keisuke69さんによるAWS LambdaのPricingを読み解く - Qiitaでした。

はじめに

S3でファイル授受をする場合、S3のsigned urlを使うケースが多いかと思います。
しかしながら、たとえば会社の規則でダウンロードURLとパスワードを別のメールで送ることになっているような場合、signed urlが使いにくい場合があります。S3でベーシック認証やDigest認証が欲しいという要望が根強いのはこういう理由も多いようです。
そこで今回は、LambdaとS3を使って、パスワード認証の出来るファイル便を作ってみました。
デモサイト:https://lambda-passwordauth.s3-us-west-2.amazonaws.com/upload.html

仕組み

大まかな仕組みは、以下のようになります。

アップロード部分

ファイルアップロードまで簡単で、AWS SDK for JavaScriptを使って、Cognitoのアノニマス認証をしてからファイルをアップロードします。Cognitoには、S3へのput権限だけついたロールを紐付けしておきます。こちらのエントリ(AWS Lambdaで顔認識)のアップロード部と内容はほぼ同じです。送信するファイルをS3にアップロード後、パスワードを入れたファイルをrequest.jsonという名前のファイルでアップロードします。

Lambdaの処理

Lambdaファンクションでは、クライアントからrequest.jsonがアップロードされた所から処理をはじめます。

var aws = require('aws-sdk');
var fs = require('fs');
var http = require('http');
var CryptoJS = require("crypto-js");
exports.aws = aws;

var s3;
var tmpDir = "/tmp";
var zipFile = tmpDir+"/archive.zip";
var bucket;
var uuid;
var password;
var ctx;
exports.handler = function(event, context) {
  ctx = context;
  bucket = event.Records[0].s3.bucket.name;
  var key = event.Records[0].s3.object.key;
  var region = event.Records[0].awsRegion;
  var op = event.Records[0].eventName;
  if (op !== "ObjectCreated:Put") {
    context.done(null, 'op');
    return;
  }
  var regexp = /upload\/([0-9a-z\-]*)\/request.json/;
  var match = regexp.exec(key);
  if (match === null) {
    context.done(null, 'except upload');
    return;
  }
  uuid = match[1];
  s3 = new aws.S3({
    region : region
  });
  var prefix = "upload/" + uuid + "/";
  loadAllFiles(createZipFile,bucket, prefix);
};
function createZipFile() {
  var request = JSON.parse(fs.readFileSync(tmpDir+"/request.json"));
  password = request.password;
  var child = require('child_process').spawn('java', [ "-cp", "/var/task:/var/task/*", "Zip",
  zipFile,password,tmpDir,"request.json"]);
  child.stdout.on('data', function(data) {
    console.log("stdout:" + data);
  });
  child.stderr.on('data', function(data) {
    console.log("stderr:" + data);
  });
  child.on('close', function(code) {
    uploadZip();
  });
};
function uploadZip(){
  var body = fs.readFileSync(zipFile);
  var passwordDir = CryptoJS.SHA3(password).toString(CryptoJS.enc.Hex);
  var key = "download/"+uuid+"/"+passwordDir+"/archive.zip";
  console.log("upload:"+key);
  s3.putObject({
    Bucket:bucket,
    Body : body,
    Key:key
  }, function(err) {
     if(err){
       console.log(err);
       ctx.done(null,"create:"+key);
     }else{
       ctx.done(null,"error:"+key);
     }
  });
};
function loadAllFiles(callback, bucket, prefix) {
  s3.listObjects({
    Bucket : bucket,
    Prefix : prefix
  }, function(err, data) {
    if (err) {
      throw err;
    } else {
      var cb = (function(jobNum, callback) {
        var counter = 0;
        return function() {
          if (++counter===jobNum) {
            callback();
          }
        };
      })(data.Contents.length, callback);
      for (var i = 0; i < data.Contents.length; i++) {
        console.log(data.Contents[i].Key);
        loadObject(cb, bucket, data.Contents[i].Key);
      }
    }
  });
};
function loadObject(callback, bucket, key) {
  s3.getObject({
    Bucket : bucket,
    Key : key
  }, function(err, data) {
    if (err) {
      throw err;
    } else {
      console.log("load:" + key);
      if(key.endsWith("/")===false){
        var paths = key.split('/');
        var fileName = paths[paths.length-1];
        console.log(tmpDir + '/' + fileName);
        fs.writeFileSync(tmpDir + '/' + fileName, data.Body);
      }
      callback();
    }
  });
};

String.prototype.endsWith = function(suffix) {
  return this.indexOf(suffix, this.length - suffix.length) !== -1;
};

はじめに、request.jsonのパスから、uuidを取得します。次に、アップロードされたファイルをすべて/tmpにコピーします。
コピー後、request.jsonから取得したパスワードを使って、zipファイルを作成します。Lambda環境ではzipコマンドがなさそうだったので、今回はzip4jを使ってパスワード付きzipを作成しました。なお、Lambda functionをzipで固めてアップロードすると、/var/taskに展開されます。ここは読み取りは出来るため、クラススパスを通せばjavaを起動することが出来ます。このため、javaプロセスを起動するところで、-cp /var/task/:/var/task/* となるように設定をしています。
zipを作成後、S3の/download以下にファイルをputします。/downloadの下にuuidでパスを作り、さらにパスワードをSHA3でdigestした値でパスを作ります。以下のようなURLでファイルが配置されることになります。

http://lambda-passwordauth.s3.amazonaws.com/download/f982c3f3-e90e-2dec-8386-34365763xxxx/1e2e9fc2002b002d75198b7503210c05a1baac1234567a3c6d93bcce3a50d7f00fd395bf1647b9cas8d1afcc9c76c289b0c9383ba386a956da4b38934417789e/archive.zip

f982c3f3-e90e-2dec-8386-34365763xxxxの部分がuuid,1e2e9fc2002...934417789eの部分が、パスワードをsha3でdigestしたものです。
クライアント側はこのパスでzipが置かれることが分かっているので、HEADリクエストでポーリングしてzipが出来るのを待ちます。
zip完成後、ダウンロード用のURLが表示されるので、これをファイルを送りたい相手に送ります。設定したパスワードは別メールなどで送ります。

ダウンロード部分

ダウンロード用のhtmlのパスは一律で、今回のデモ環境では https://lambda-passwordauth.s3-us-west-2.amazonaws.com/upload.html?id=${uuid} となります。${uuid}の部分は、アップロード時に作成されたuuidが入ります。
ダウンロード用のhtmlでは、入力されたパスワードを使って、zipファイルのURLを作り、HEADリクエストで存在チェックをします。入力したパスワードが合っていれば、Lambdaで作ったURLと同じURLが出来るため、ファイルがダウンロードできます。また仮にURLが漏れても、zipにもパスワードがかかっているため、パスワードがなければファイルを取り出すことはできません。

おわり

今回作ったデモでは、宅ファイル便的なやつをLambdaとS3で実現できました。これに加えて、LambdaでS3の利用量制限 を使ってS3の利用量制限をかけたり、Lambdaでアクセスカウンタを作った を使ってダウンロード数のカウントをしたりすると、より動的っぽいサイトに出来そうです。

明日21日目はshimy@githubさんによるおもしろエントリの予定です。お楽しみに!

 

AWS麻雀牌の作り方

はじめに

片山まさゆきと苗字が同じ、というだけで麻雀を始めた男、片山です。

JAWS-UGメンバーであれば一度は目にしたことのある「AWSカルタ」(クラウドパック吉田さん謹製)を見て、麻雀も作りたいなぁ、と前から思っていたところ、今年の社内忘年会でカジノが開かれることになり、ディーラーとして参加するチャンス!と思い、AWS麻雀牌作製への道に乗り出しました。

f:id:c9katayama:20141215002239p:plain

調査フェーズ

「麻雀牌 自作」などのキーワードで検索したところ、オリジナル牌を作ってくれるお店が1件出てきました。

市川屋 
オリジナルデザインの牌を削ってくれるそうで、かなり本格的です。しかしながらやはり高い。今回はフルスクラッチになるため、削って色を入れてもらうところまですると7万円近くかかってしまいます。さすがにこれは無理ということで、白牌だけを144枚購入して、自分で絵柄を入れることにしました。

白牌だけを探す

いくつか白牌だけで売っているようなところもありましたが、ほとんどが在庫切れ or デッドリンクでしたが、こちらの囲碁クラブさんでは取り扱いがありました。

囲碁クラブ

Amazon楽天でも店を開いており、いくつか商品ラインナップがあります。 

パイパンの単品売り  144個セット

パイパンの単品売り  144個セット

 

 Amazonで144個で1万円でしたが、在庫状況が分からなかったので、まずは電話をしてみました。確認したところ、在庫はあるということでした。また点棒なども欲しかったため、箱もセットで売ってくれるか?と質問したところ、それであれば13,000円ですということでした。

しかしながら、いろいろ見ていると30個で1,400円で販売しているページもあり、単純にこれを5セット買った方が安いことに気が付きました。


【楽天市場】麻雀 パイパン30個組:囲碁ラボJAPAN

 もちろんこれだと箱や点棒が付かないのですが、麻雀牌のサイズを確認して、ほぼ同じサイズの牌が使われているセットを購入して、入れ替えることしました。ネットで確認したところ、雷神というセットが安くてサイズもよさそうでした。 

高級麻雀牌 雷神?JANPAI-RAIJIN

高級麻雀牌 雷神?JANPAI-RAIJIN

 

  これが4,500円ぐらいなので、144個+箱よりも安く手に入る上に、ちゃんとした牌もついてくるのでお得です。

なお余談ですが、30個入り4セットにはトレーもついてきたので、入れ替えは簡単でした。

f:id:c9katayama:20141212043947j:plain

絵柄の貼り付け方法

次に絵柄の貼り付け方法を考えました。順当に行くと、ステッカー素材の用紙に絵柄を印刷してカット->貼り付けとなりますが、この方法はカットが非常に手間なので、出来ればカット済みで適切なサイズのものがあれば..と思い家電量販店に行ったところ、非常に良いものが売られていました。 

エレコム フォトシール(ハガキ用)16面×5 EDT-PSK16

エレコム フォトシール(ハガキ用)16面×5 EDT-PSK16

 

 もともとはプリクラ的なものをご家庭で作れる用のシールのようですが、サイズが麻雀牌にぴったりです。またシールの角が丸く、はがれにくいのもプラスです。今回は「こすれに強い」という謳い文句の耐水フォトシートを選択しました。画質重視なら、同一サイズの高画質タイプも良いと思います。

作製開始

アイコンを用意して印刷、貼り付けと行っていきます。

アイコンはAWS Simple Iconをベースに、文字を加えて行きました。また色を統一するためPhoto Shopで色を揃えて、最後にPower Pointに台紙を作って並べて行きます(とさらっと書いてますが、ここが一番時間がかかりました。。)

f:id:c9katayama:20141215000323p:plain

出来上がったら、雛形をシールに印刷していきます。印刷はかなり濃いめにした方がよいようです。また背景の点線枠を印刷しないほうが、たぶんきれいにできます(私は印刷設定をし損ねたので若干黒線が入ってしまいました。。。)

f:id:c9katayama:20141212043925j:plain

印刷終了後、どんどん貼っていきます。牌は念のため、脱脂した方が良いでしょう。

f:id:c9katayama:20141212043938j:plain

貼り終えたら完成です!

f:id:c9katayama:20141212050617j:plain

実際にやってみて

 はじめは絵柄が頭に入らず若干混乱しましたが、慣れると問題なく遊ぶことが出来ます。AWSアイコンならではのオリジナル役も楽しめます。

ゲームの説明のために当日使った資料をアップデートしたので、ルールや牌の説明はこちらを参照してください。

 またアイコンと印刷用のpptもあるので、適宜ご利用ください。

AWS麻雀アイコンセット

AWS麻雀印刷用雛形

ぜひハッカソンやりましょう!

f:id:c9katayama:20141212210146j:plain

 

AWS Lambdaを使って、S3のデータ利用量を制限する

AWS Lambda

Qiitaで書きました。プレビューが横で見れたり、画像がアップしやすいので、はてぶよりも書きやすい。。
http://qiita.com/c9katayama/items/706f8198d76f2232e874

Lambdaで画像に描画する

AWS Lambda

Lambdaからのjava呼び出しで描画系が使えることが確認できたので、Lambda内で画像に描画してみます。

Lambdaコード

Lambda Functionは、先日のエントリとほぼ一緒です。Java実行後に、/tmpに生成された画像をS3に吐きだす部分を追加しています。

var aws = require('aws-sdk');
var fs = require('fs');
var javaBucket = "lambda-java";
var javaBucketRegion = "us-west-2";
var mainClass = "GraphicsTest";

exports.handler = function(event, context) {
  loadAllFiles(function() {
    var spawn = require('child_process').spawn;
    var child = spawn('java', [ "-cp", "/tmp:/tmp/*", mainClass,
        JSON.stringify(event,null,2) ]);
    child.stdout.on('data', function(data) {  console.log("stdout:" + data);  });
    child.stderr.on('data', function(data) {  console.log("stderr:" + data);  });
    child.on('close', function(code) {  var s3 = new aws.S3({  params : {
          Bucket : javaBucket,   Key : "result.jpg"   }
      });
      var body = fs.readFileSync('/tmp/result.jpg');
      s3.putObject({ Body : body  }, function(err) {
        if (err) {   context.done("ERROR", err);
        } else {  context.done(null, 'put result');    }
      });
    });
  });
};
function loadAllFiles(callback) {
  var s3 = new aws.S3({  region : javaBucketRegion });
  s3.listObjects({   Bucket : javaBucket
  }, function(err, data) {  if (err) {
      throw err;  } else {
      var cb = createCounter(data.Contents.length, callback);
      for (var i = 0; i < data.Contents.length; i++) {
        loadObject(cb, data.Contents[i].Key);
      }
    }
  });
}
function loadObject(callback, key) {
  var s3 = new aws.S3({  region : javaBucketRegion });
  s3.getObject({  Bucket : javaBucket,  Key : key
  }, function(err, data) {  
    if (err) {   throw err; } else {
      console.log("load:" + key);
      fs.writeFileSync('/tmp/' + key, data.Body);
      callback();
    }
  });
}
function createCounter(length, callback) {
  var counter = 0;
  return function() {
    if (++counter == length) {
      callback();
    }
  }
}

Javaコード

画像に描画を行うコードになります。作った画像は、/tmp/result.jpgに吐きだします。jarファイルには、ipaフォントと、元になる画像を入れておきます。
画像は、手元にあるイケメン画像を選びました。


import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;

public class GraphicsTest {

  public static void main(String[] args) throws Exception {
    Font font = Font.createFont(Font.TRUETYPE_FONT,
        GraphicsTest.class.getResourceAsStream("/ipagp.ttf"));
    GraphicsEnvironment ge = GraphicsEnvironment
        .getLocalGraphicsEnvironment();
    ge.registerFont(font);
    
    BufferedImage img = ImageIO.read(GraphicsTest.class
        .getResourceAsStream("m2o.jpg"));

    Graphics2D g2 = (Graphics2D) img.getGraphics();
    g2.setFont(new Font("IPAPGothic", Font.BOLD, 35));
    g2.setPaint(Color.black);
    g2.fillRect(150, 100, 150, 20);
    g2.setPaint(Color.white);
    g2.drawString("HPCチョットデキル", 60, 300);
    ImageIO.write(img, "png", new File("/tmp/result.jpg"));
    System.out.println("draw end");
  }
}


描画した結果です。



日本語描画もOKですね。Cognito+S3+Lambdaで、脳内メーカーぐらいならEC2レスで作れそうです。
次はOpenCVを動かしてみたいと思います。


なお、以下実行ログです。メモリ利用量が73MB、実行時間が9.8秒でした。

Logs
----
START RequestId: 1646081e-75c6-11e4-ae89-2105a6e34bc3
2014-11-26T23:44:08.875Z	1646081e-75c6-11e4-ae89-2105a6e34bc3	load:result.jpg
2014-11-26T23:44:09.515Z	1646081e-75c6-11e4-ae89-2105a6e34bc3	load:eval.jar
2014-11-26T23:44:16.774Z	1646081e-75c6-11e4-ae89-2105a6e34bc3	stdout:draw end

END RequestId: 1646081e-75c6-11e4-ae89-2105a6e34bc3
REPORT RequestId: 1646081e-75c6-11e4-ae89-2105a6e34bc3	Duration: 9759.41 ms	Billed Duration: 9800 ms 	Memory Size: 256 MB	Max Memory Used: 73 MB	

Message
-------
put result

Lambda Javaで使えるフォント一覧

AWS Lambda

一覧取得コード

  GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
  Font fonts[] = ge.getAllFonts();
  for (int i = 0; i < fonts.length; i++) {
    System.out.println(fonts[i].getName());
  }


結果

続きを読む

Lambda開発環境をEclipseで作る

AWS Lambda

やはりEclipse派の私としては是非ともEclipseでLambdaを開発したい、コードアシストを使いたい、ローカルで動かしたい、ということで開発できる環境を作ってみました。
まだ試行錯誤中ですが、Node.jsは使い始めたばかりでお作法が分かっていないので、より良い方法があれば是非教えて頂きたいと思いますm(_ _)m

続きを読む