月別アーカイブ: 2018年7月

Playframework2.6でファイルアップロードのユニットテスト

Multipartでファイルアップロードするapiのテストを行いたかったんだけど、めちゃくちゃハマったというか、わからなかったのでここにメモする!

ポイント1 「アップロードするファイルをリソースから取得する」

まず、テストケースで使用するリソース(今回はアップロードするファイル)はどこに保存すればいいのか分からんね!

そんな時は次のコマンドを叩けばわかるぞ!

sbt "show test:resourceDirectory" 
> [info] Test / resourceDirectory
> [info] /Users/honyarara/project/test/resources

そしてこのファイルを取得するにはこうだ!

/Users/honyarara/project/test/resources/testfile.jpg を取得する時は。

val fileUrl = getClass.getResource("/testfile.jpg")

先頭のスラッシュが無いとnullになっちゃうので注意!

 

ポイント2 「multipart/form-dataのFakeRequestを作成する」

WEBに転がってる情報が古くて正解になかなかたどり着けなかったよ。

最初に リソースから取得したファイルをTemporaryFileに変換する。
既に存在するファイルのTemporaryFileって何言ってるんだ?と思うかもしれないが、そういうもんだ。

val tempFile = SingletonTemporaryFileCreator.create(Paths.get(fileUrl.toURI))

で、このtempFileを使ってFilePartを作成する

val filepart = FilePart[TemporaryFile]("imageFile","testfile.jpg",Some("image/jpeg"),tempFile)

次にファイルと同時に送信するフォーム情報を作成する

val data:Map[String,Seq[String]] = Map(
  "title" -> Seq("パイスラッシュ女子")
)

後は、ここまでの情報を組み立てればOK

val formData = MultipartFormData[TemporaryFile](
	dataParts = data,files = Seq(filepart),badParts = Seq())

val request = FakeRequest(POST,"/upload")
	.withMultipartFormDataBody(formData);	

これで “multipart/form-data” なFakeRequestができる。

 

ポイント3 「実行する!」

実行時にはroute経由でやらないと上手く動かなかった。

val result = route(app, request).get
status(result) mustBe OK

 

まとめると

import java.nio.file.Paths
import play.api.libs.Files.SingletonTemporaryFileCreator
import play.api.libs.Files.TemporaryFile
import play.api.mvc.MultipartFormData
import play.api.mvc.MultipartFormData.FilePart

class UploadControllerSpec extends PlaySpec with GuiceOneAppPerTest with Injecting
{
  "画像をuploadするとOkを返すよ" in {

  	// 送信するフォームデータ
	val data:Map[String,Seq[String]] = Map(
	  "title" -> Seq("パイスラッシュ女子大生")
	)

	// リソースからファイルを取得してTemporaryFileを作成する
  	val fileUrl = getClass.getResource("/testfile.jpg")
  	val tempFile = SingletonTemporaryFileCreator.create(Paths.get(fileUrl.toURI))

  	// ファイルパートを作成する
  	val filepart = FilePart[TemporaryFile](
  			"imageFile","testfile.jpg",Some("image/jpeg"),tempFile)

  	// 組み合わせてMultipartFormDataを作成
  	val formData = MultipartFormData[TemporaryFile](
  		dataParts = data,files = Seq(filepart),badParts = Seq())

  	// FakeRequestを作成
  	val request = FakeRequest(POST,uploadEndpointUrl)
  		.withMultipartFormDataBody(formData)

  	// 実行
  	val result = route(app, request).get
  	status(result) mustBe OK
  }
}

PEMのBASE64文字列からPrivateKeyを生成する方法

PEMをテキストファイルとして読み込んでjava.security.PrivateKeyを作る必要があったんだけど、読み取れるものと読み取れないものがあってちょっと困っていた。
読み取れないものは「not a sequence at ・・」みたいなエラーになってる。

で、良く見るとヘッダが異なる事に気がついた。
要するに PKCS#8 は読み込めるけど PKCS#1 はすんなり読んでくれないんですね。

ちなみに 見分け方は
-----BEGIN PUBLIC KEY-----
で始まってたら PKCS#8

-----BEGIN RSA PUBLIC KEY-----
で始まってたら PKCS#1

で、具体的なコードは下記のようになる。

PKCS8の時は簡単、PKCS8EncodedKeySpecにそのままバイト配列を食わせればOK。

String code "BASE64の文字列";
byte[] bytes = Base64.getDecoder.decode(code);

KeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(spec);

PKCS1の時はDerInputStreamを使ってパラメータを分解して、その情報からPrivateKeyを作成する。

String code "BASE64の文字列";
byte[] bytes = Base64.getDecoder.decode(code);

DerInputStream derReader = new DerInputStream(bytes);
DerValue[] seq = derReader.getSequence(0);

BigInteger modulus = seq(1).getBigInteger;
BigInteger publicExp = seq(2).getBigInteger;
BigInteger privateExp = seq(3).getBigInteger;
BigInteger prime1 = seq(4).getBigInteger;
BigInteger prime2 = seq(5).getBigInteger;
BigInteger exp1 = seq(6).getBigInteger;
BigInteger exp2 = seq(7).getBigInteger;
BigInteger crtCoef = seq(8).getBigInteger;

KeySpec spec = new RSAPrivateCrtKeySpec(
     modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(spec);

あ、ちなみにですが、”BASE64の文字列”はヘッダとフッタの間に記述されている文字列です。
-----BEGIN RSA PUBLIC KEY-----
(ここの文字列)
-----END RSA PUBLIC KEY-----