Jerseyで作成したRESTサービスをJUnitでテストする(Multipart)
ファイルアップロードなどMultipartでPOSTするRESTサービスのテストの書き方メモ
リソースクラスの例
package jp.yustam.jersey.resources; import java.io.InputStream; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import com.sun.jersey.multipart.FormDataParam; @Path("file") public class File{ @POST @Consumes( { MediaType.MULTIPART_FORM_DATA }) @Produces( { MediaType.TEXT_PLAIN }) @Path("upload/{fileName}") public Response upload(@FormDataParam("file") InputStream stream, @PathParam("fileName") String fileName) { return Response.ok().entity("OK").build(); } }
テストクラスの例
package jp.yustam.jersey.resources; import static org.junit.Assert.assertEquals; import java.io.File; import java.io.IOException; import javax.ws.rs.core.MediaType; import org.junit.Test; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.core.header.FormDataContentDisposition; import com.sun.jersey.multipart.FormDataBodyPart; import com.sun.jersey.multipart.FormDataMultiPart; import com.sun.jersey.test.framework.JerseyTest; import com.sun.jersey.test.framework.WebAppDescriptor; public class FileTest extends JerseyTest { public FileTest() { super(new WebAppDescriptor.Builder("jp.yustam.jersey.resources").build()); } @Test public void testUpload() throws IOException { File file = File.createTempFile("TMP", ".tmp"); FormDataMultiPart form = new FormDataMultiPart(); form.bodyPart(new FormDataBodyPart(FormDataContentDisposition .name("file").build(), file, MediaType.APPLICATION_OCTET_STREAM_TYPE)); WebResource wr = resource().path("file/upload/fileName"); ClientResponse response = wr.type(MediaType.MULTIPART_FORM_DATA) .post(ClientResponse.class, form); assertEquals(response.getEntity(String.class), "OK"); file.delete(); } }
Jerseyで作成したRESTサービスをJUnitでテストする
Jersey Test Frameworkを使用する
Chapter 7. Jersey Test Framework
いくつか種類があるみたいですが今回使用したのは「jersey-test-framework-grizzly2」
pom.xmlに以下の依存関係を追加します。
<dependency> <groupId>com.sun.jersey.jersey-test-framework</groupId> <artifactId>jersey-test-framework-grizzly2</artifactId> <version>1.14</version> <scope>test</scope> </dependency>
Testクラスを書く
web.xmlでパスを切っていたのですがテストクラスではweb.xmlの指定は必要ないみたいです。
テストクラス作成時のWebResourceのパス指定にて「services/sample/login/id/pw」でなく
「sample/login/id/pw」となるので注意
リソースクラスの例
package jp.yustam.jersey.resources; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @Path("sample") public class MyService { @GET @Path("/login/{id}/{pw}") @Produces( { MediaType.TEXT_PLAIN }) public Response login(@PathParam("id") String id, @PathParam("pw") String pw) { return Response.ok().entity("OK").build(); } }
web.xmlの例
<servlet> <servlet-name>MyRESTService</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <init-param> <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name> <param-value>com.sun.jersey.api.core.PackagesResourceConfig</param-value> </init-param> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>jp.yustam.jersey.resources</param-value> </init-param> <init-param> <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>MyRESTService</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping>
テストクラスの例
package jp.yustam.jersey.resources; import static org.junit.Assert.assertEquals; import org.junit.Test; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.test.framework.JerseyTest; import com.sun.jersey.test.framework.WebAppDescriptor; public class MyServiceTest extends JerseyTest { public MyServiceTest() { super(new WebAppDescriptor.Builder("jp.yustam.jersey.resources").build()); } @Test public void testLogin() { WebResource wr = resource().path("sample/login/id/pw"); ClientResponse response = wr.get(ClientResponse.class); assertEquals(response.getEntity(String.class), "OK"); } }
Amazon ElasticMapreduceメモ
Amazon ElasticMapreduceのテストを兼ねて性能測定を行ったので分かったことをメモ
ジョブフローの起動
ジョブフローを登録してからステータスが「RUNNING」に変わるまで4分~6分かかる
立ち上げるインスタンスの数が変わってもこの時間は変わらない
MapTaskの数
EMRに限らずHadoopの動作ですがインスタンス数などの起動設定に必要なのでメモ
入力に1GBのファイルを使用したところどのような構成にしても全て16個のMapTaskが生成された
MapTaskの数 = 入力ファイルのサイズ ÷ 64MB
圧縮した場合はBZip2とGZipで試した結果コーデックに関わらず1つの入力ファイルは
1つのMapTaskに割り当てられるみたい
ブロックサイズを変更すれば分割するサイズを変更することが可能
"fs.s3n.block.size"で指定する(デフォルトは67108864)
// ブロックサイズを32MBに設定 conf.setInt("fs.s3n.block.size", (67108864 / 2));
EC2インスタンス数の上限
EC2の同時起動台数上限を超えてインスタンスを立ち上げようとしたときキャンセルされる
(ステータスはFAILEDとなる)
Job flow failed with reason: The requested number of instances exceeds your EC2 quota
EC2のインスタンス同時起動台数はデフォルトで20台なのでEMRを使用する場合は
余裕をもって使用できるよう増やしておく
圧縮について
ローカルからS3への転送がすごく遅いので圧縮した方が嬉しいことが多い
圧縮ファイルを入力に渡すとHadoopが解凍してくれるので圧縮するだけで使用可能。
スプリット | 速度 | 圧縮率 | |
BZip2 | ○ | 遅い | 高い |
GZip | × | 速い | 中 |
Snappy | × | かなり速い | 低い |
EntityTooLarge Your proposed upload exceeds the maximum allowed size
以前作成したブラウザからS3へ直接ファイルをアップロードする画面を使って
300MBほどのデータをアップロードしたところ以下のようなエラーが発生した
HTTP/1.1 400 Bad Request <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN"> <html> <head> <title></title> </head> <body> <code>EntityTooLarge</code>Your proposed upload exceeds the maximum allowed size105258698B06FF034709E32IEq3fcwyZVtnEvU0mHBUVsz+Rvl7jZAyCPGYqsKuZU5fr5f/6WrbJ8hZsIzGFbI61048576 </body> </html>
Amazon S3: Browser-Based Uploads using POST
シグネチャ生成時のポリシー「content-length-range」にアップロードを許可するファイルの
サイズ(最小値と最大値)を設定することができるらしい
String policy_document = "{\"expiration\": \"" + limit + "\"," + "\"conditions\": [" + "{\"bucket\": \"" + bucket + "\"}," + "[\"starts-with\", \"$key\", \"" + path + "\"]," + "{\"acl\": \"private\"}," + "{\"success_action_redirect\": \"" + redirectURL + "\"}," + "[\"starts-with\", \"$Content-Type\", \"\"]," + "[\"content-length-range\", 0, 1048576]" + "]" + "}";
ソースを確認したら「1048576(100MB)」となっていたので修正したら大丈夫でした
java.lang.NoClassDefFoundError: org/codehaus/jackson/map/deser/std/StdDeserializer
Jersey-JSONとAWS-Java-SDKの併用時はjacksonのバージョンに注意
java.lang.NoClassDefFoundError: org/codehaus/jackson/map/deser/std/StdDeserializer
StdDeserializer.StringDeserializer (Jackson JSON Processor)
1.9系からはStdDeserializerを使用しないようになっています。
エラー発生時のpom.xmlを確認
・・・ <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk</artifactId> <version>1.3.20</version> </dependency> ・・・ <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-json</artifactId> <version>1.14</version> </dependency> ・・・
依存関係を見るとaws-java-sdk(v1.3.20)ではjackson(v1.8.9)を使用しているので
先に書くとv1.8.9が使用される
pom.xmlを修正し依存関係の順番を変更
・・・ <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-json</artifactId> <version>1.14</version> </dependency> ・・・ <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk</artifactId> <version>1.3.20</version> </dependency> ・・・
これでjackson1.9系を使用するようになる
APIからS3のディレクトリを削除する
S3のディレクトリを配下のオブジェクトごと一括で削除したいと思ったのですが
ディレクトリを削除するというAPIは用意されていないようでオブジェクトを
ひとつずつ削除する必要があるそうです
AWS Developer Forums: Delete folder using Java API ...
if (s3Client.doesBucketExist(BUCKET_NAME)) { ObjectListing objects = s3Client.listObjects(BUCKET_NAME, PREFIX); for (S3ObjectSummary objectSummary : objects.getObjectSummaries()) { s3Client.deleteObject(BUCKET_NAME, objectSummary.getKey()); } }
AmazonLinuxにGitLabを導入
GitLabをAmazon EC2の所謂Amazon Linuxにセットアップしてみました。
今回使用したAMIのIDは『ami-2819aa29』です。
gitlabhq/doc/installation.md at stable · gitlabhq/gitlabhq · GitHub
CentOS6にGitLabをインストールする方法 | Ryuzee.com
こちらの記事に丁寧に書かれていたのでほとんどその通りなのですが
Amazon Linuxでは少し足りない部分があったのでメモ程度に
Amazon Linuxではrootでsshできないのでec2-userでログインしsudoで実行
最初に以下のコンポーネントが足りないので追加インストール
yum install make yum install gcc yum install gcc-c++ yum install sqlite-devel
checkinstallがうまくインストールできなかったのでruby1.9.3はそのままインストール
cd /tmp/ruby-1.9.3〜 ./configure make make install
bundle install実行時にsqlite3-1.3.6が無いと言われるのでインストール
Could not find sqlite3-1.3.6 in any of the sources
gem install sqlite3-ruby
最後にgitlab.confにてポートを80番以外にする場合はListenを追加
Listen 8080