Quantcast
Channel: Postmanタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 470

IntelliJ+JerseyとPostmanでRESTfulAPIサンプル(JSON)

$
0
0

目的

IntelliJ IDEAで、JerseyでRESTfulAPIのサンプルを作る覚書。

ゴール

  • Tomcat起動してブラウザでAPIを呼び出す(GETまで)
  • Postmanを使ってAPIの動作確認
  • とりあえずJSONで返す

環境など

ツールなどバージョンなど
MacbookPromacOS Mojave 10.14.5
IntelliJ IDEAUltimate 2019.3.3
JavaAdoptOpenJDK 11
apache maven3.6.3
Jersey2.30.1
JUnit5.6.0
Tomcatapache-tomcat-8.5.51
Postman7.19.1

jdkやmaven、Tomcatのインストールは済んでいるものとします。
※コマンドラインでmavenコマンドを使わない場合は、mavenのインストールは不要です。(IntelliJにバンドルされています)

Postmanはこちらからインストールできます。
かつてはChromeの拡張機能で入れられたのですが、スタンドアロンアプリに変わっています。
同様の拡張機能はまだあるので、それらでも構わないと思います。

mavenプロジェクトの作成

mavenのJerseyを使ったWebアプリをまず作成します。
こちらを参考に、コマンドで作成します。
https://docs.huihoo.com/jersey/2.13/getting-started.html#new-webapp

$mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-webapp \-DarchetypeGroupId=org.glassfish.jersey.archetypes \-DinteractiveMode=false\-DgroupId=com.example \-DartifactId=simple-service-webapp -Dpackage=com.example \-DarchetypeVersion=2.30.1

groupIdartifacgtIdpackageは任意に変えてください。
TomcatではなくてGrizzlyで動かすので十分な場合は、#new-from-archetypeの項にあるコマンドで十分かと思います。

IntelliJ IDEAで開く

IntelliJ IDEAにプロジェクトをインポートします。

  • 起動画面で[Import Project]を選ぶ

project-import.png

  • プロジェクトフォルダを選び、[Open]をクリック
  • Mavenを選ぶ

project-type-maven.png

  • [Finish]をクリック

プロジェクトが開きます。

pom.xmlを編集する

pom.xmlを開いたら、Enable Auto Importをクリックしておきましょう。

1. javaバージョン

まず、javaバージョンが1.7になっているのを11に変更します。

pom.xml
<build><finalName>simple-webapp</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.5.1</version><inherited>true</inherited><configuration><source>11</source><!-- ここ --><target>11</target><!-- ここ --></configuration></plugin></plugins></build>

2. 依存ライブラリの追加

(1)Json

Jsonを使いたいので、コメントアウトされている以下のコメントを外します。

pom.xml
<dependency><groupId>org.glassfish.jersey.media</groupId><artifactId>jersey-media-json-binding</artifactId></dependency>

(2)JUnit5

JUnit5を<dependencies>タグ下に追加します。

pom.mxl
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.6.0</version><scope>test</scope></dependency>

※JUnitは、juint.junitから移動した模様です。

3. MyResourceクラスを眺める

Jerseyでどのように書くのか全く見たことがない、などの場合は、MyResouce.javaを開いて、眺めてみるとよいでしょう。

親切にコメントで何をやっているか説明してくれているので、それほど難しいことではないと思います。
ここでは、以下のことが読み取れれば問題ないかと思います。

  • @Path("myresource")で、サーバーのルートからのパスを指定している
  • getIt()メソッドは、
    • @GETなのでGETメソッドで呼び出される
    • @Produces(MediaType.TEXT_PLAIN)なので、text/plainな文字列として提供される

POST@Postだろうなとか、そういうことが想像できれば十分です。

あ、あとでcurlで動かす時の為に、"Got It!"の後に改行を入れておいたほうがいいかもしれません。
二つぐらい入れておいたほうが見やすいかもです。

MyResource.java
publicStringgetIt(){return"Got it!\n\n";}

4. 動かしてみる

(1)Tomcatから起動

Tomcatで起動する設定をし、(前記事参照)、実行します。

  • [Jersey resource]のリンクをクリック
launch_index.png
  • 下記のようなページが表示されればOK
gotit.png

(2)curlコマンドで実行

APIなので、curlコマンドでも叩けないとね。

urlは、Tomcat版でブラウザを開いた時のをコピーしてくると楽です。

$curl http://localhost:8080/simple_webapp_war_exploded/webapi/myresource
Got it!

$

改行を入れとかないと出力を見つけるのが大変です(汗)

-iオプションを入れると、詳細が見られます。

$curl -i http://localhost:8080/simple_webapp_war_exploded/webapi/myresource
HTTP/1.1 200 
Content-Type: text/plain
Content-Length: 9
Date: Tue, 10 Mar 2020 06:53:25 GMT

Got it!

(3) Postmanで実行

Postmanも使ってみます。
Tomcatでアプリを起動しておく必要があります。(サーバーが動いてないと当然反応はできませんので)

  • Request URLに、APIへのパスを入力
    • ブラウザからコピーするのが早いです
  • メソッドはGETを選択
postman.png
  • [Send]ボタンをクリック

下の欄に結果が表示されているはずです。

postman_get_result.png

サンプルAPIを作る

1. モデルクラス

こんなモデルクラスでデータを登録して、読み書きするAPIを作ることを考えます。

publicclassEmployee{privateintid;privateStringfirstName;publicEmployee(){}publicEmployee(intid,StringfirstName){this.id=id;this.firstName=firstName;}publicintgetId(){returnid;}publicvoidsetId(intid){this.id=id;}publicStringgetFirstName(){returnfirstName;}publicvoidsetFirstName(StringfirstName){this.firstName=firstName;}}

(独り言)Kotlinならdata classで簡単なのになー

追記
空のコンストラクタは必須なので、忘れないでください。
(無いと500エラーになります。内部的には、JSON Binding deserialization error: javax.json.bind.JsonbException: Internal error: nullという例外で落ちています。)

2. リポジトリクラス

データの読み書きは以下のクラスを介して行います。
本来はデータベースなどから読み出すべきですが、ここではそれは重要ではないので、とりあえずリストを持っておいて使うことにします。

EmployeeRepository.java
publicclassEmployeeRepository{privatestaticEmployeeRepositoryinstance=newEmployeeRepository();privateList<Employee>employeeList;privateEmployeeRepository(){employeeList=newArrayList<>();}publicEmployeeRepositorygetInstance(){returninstance;}publicList<Employee>selectAll(){returnemployeeList;}publicEmployeeselect(intid){for(Employeeemployee:employeeList){if(employee.getId()==id){returnemployee;}}thrownewEmployeeNotFoundException();}publicsynchronizedvoidinsert(intid,StringfirstName){try{select(id);}catch(EmployeeNotFoundExceptione){// いなければ追加できるemployeeList.add(newEmployee(id,firstName));return;}// 同じIDが存在したら追加できないthrownewDuplicateIdException();}publicsynchronizedvoidupdate(intid,StringfirstName){Employeeemployee=select(id);employee.setFirstName(firstName);}publicsynchronizedvoiddelete(intid){Employeeemployee=select(id);employeeList.remove(employee);}}

EmployeeNotFoundExceptionDuplicateIdExceptionはそれぞれこんなクラスです。

EmployeeNotFoundException.java
publicclassEmployeeNotFoundExceptionextendsRuntimeException{publicEmployeeNotFoundException(){super("そのIDのEmployeeは見つかりません。");}}
DuplicateIdException.java
publicclassDuplicateIdExceptionextendsRuntimeException{publicDuplicateIdException(){super("そのIDのEmployeeはすでに登録されています。");}}

3. リソースクラス

いよいよAPIの部分です。MyResourceの改造ではなくて、新たにリソースクラスを作っていきます。
(別に改造でもいいんですが)

(1)初期データ

まずは簡単に初期データを登録しておいて、GETできるようにしましょう。
先ほどのリポジトリクラスの初期化処理に、初期データを入れておきます。

EmployeeRepository.java
privateEmployeeRepository(){employeeList=newArrayList<>();employeeList.add(newEmployee(3,"Cupcake"));employeeList.add(newEmployee(4,"Donuts"));employeeList.add(newEmployee(5,"Eclair"));employeeList.add(newEmployee(8,"Froyo"));employeeList.add(newEmployee(9,"Gingerbread"));}

名前に「ピン」ときたあなたは、ほくそ笑んでおいてください(笑)
Eclipseで頑張っていたあの時代・・・

(2)GETメソッドの作成

一番簡単なGETメソッド、さらにその中でもselectAllなAPIを作ります。

EmployeeResource.java
@Path("/employees")publicclassEmployeeResource{@GET@Path("/all")@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})publicList<Employee>getAll(){returnEmployeeRepository.getInstance().selectAll();}}
  • @Path("/employees")
    • /employeesパスでアクセス
  • @GET
    • GETメソッド
  • @Path("/all")
    • 追加のパス。つまり、/employees/allでアクセスします
  • @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    • JSONとXMLで返します。
    • 将来のためにXMLも返すように指定していますが、今回はまだJSONしか返しません。
  • EmployeeRepositoryのシングルトンからリストを取得

(3)index.jspを編集する

ブラウザからAPIにアクセスできるよう、index.jspにリンクを追加します。
お好みで見栄えを整えてください。

index.jsp
<p><a href="webapi/employees/all">All Employee List</a>

(4)リデプロイ

実行ボタンを押すと、以下のポップアップが表示されます。

redeploy.png

  • [Redeply]を選択
  • [OK]をクリック
  • ブラウザをリロード

これで変更が反映されるはずです。

reload.png

(5)動作確認

新しく作ったリンクをクリックしてみましょう。

get_all_json.png

JSONの配列が返ってきていますね。
Postmanやcurlでも確認できます。

4. パスパラーメータ付きGET

(1)リソースクラスにAPIメソッドを追加

今度は、idをパラメーターとして受け取って単独オブジェクトを返すAPIを作ってみましょう。

EmployeeResource.java
@GET@Path("/{id}")@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})publicEmployeegetEmployee(@PathParam("id")intid){returnEmployeeRepository.getInstance().select(id);}

新しいのは@Path("/{id}")@PathParam()ですね。

  • @Path("/{id}")
    • 先ほど出てきた@Pathと同じで、アクセスパスの指定です。{id}は任意の値が入ることを示しています。つまり、APIを使う側は、xxxx/employee/3などのurlでアクセスすることになります。
  • @PathParam("id")
    • パス"{id}"に指定された値を受け取る変数を宣言しています。xxxx/employee/3でアクセスされると、id=3のEmployeeオブジェクトが渡ってきます。

(2)index.jspにリンクを追加

先ほどと同じく、index.jspにリンクを追加します。

index.jsp
<p><a href="webapi/employees/3">get id=3 employee</a>

(3)実行

リデプロイして、ブラウザを再読み込みし、追加したリンクをクリックしてみてください。

deploy_pathparam.png

ちゃんと返ってきました。
Postmanやcurlでも同様に確認できます。

5. クエリーパラメーター検索

今度はパスパラメーターではなく、クエリーパラメーターもやってみましょう。
/searchに対して、特定の文字(or文字列)を含むものだけ返すというのを作ってみます。

(1)検索メソッド

まずはリポジトリクラスです。
nameを受け取って、その文字列を含む名前のリストを作成して返します。

EmployeeRepository.java
publicList<Employee>search(Stringname){List<Employee>list=newArrayList<>();for(Employeeemployee:employeeList){if(employee.getFirstName().contains(name)){list.add(employee);}}if(list.size()>0)returnlist;thrownewEmployeeNameNotFoundException(name);}

EmployeeNameNotFoundExceptionは次のようなものです。

EmployeeNameNotFoundException.java
publicclassEmployeeNameNotFoundExceptionextendsRuntimeException{publicEmployeeNameNotFoundException(Stringname){super("文字列{"+name+"}を含む名前のEmployeeは存在しません。");}}

(2)リソースクラスにAPIメソッドを追加

EmployeeResource.java
@GET@Path("/all")@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})publicList<Employee>searchEmployee(@QueryParam("name")Stringname){returnEmployeeRepository.getInstance().search(name);}

@QueryParamを使います。これはなんとなく予想がついたんじゃないでしょうか^^;

(3) 実行

index.jspにリンクを追加してもいいですが、もう面倒なのでPostmanを使いました(笑)
あるいは、ブラウザのURLに直打ちでもいいですね。

postman_query_param.png

JUnitテスト

先に動作テストをしてしまっていますが、JUnitテストも書いてみます。

1.依存関係の設定

Jerseyのテストフレームワークを使います。ただ、JUnit5に対応していないようなので、ちょっとしたハックが必要です。

以下の依存関係をtestスコープに追加します。

  • Jerseyのテストフレームワーク(grizzly)
    • サーバーを仮で立てるのにgrizzlyを使います。
  • AssertJ
    • アサーションを書くのに使います。お好みで他でもいいですし、JUnitの標準を使っても構いません。

以下のようにpom.xml<dependencies>タグ内に追加します。
JUnitの下が良いでしょうね。

pom.xml
<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.test-framework.providers/jersey-test-framework-provider-grizzly2 --><dependency><groupId>org.glassfish.jersey.test-framework.providers</groupId><artifactId>jersey-test-framework-provider-grizzly2</artifactId><version>2.30.1</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.assertj/assertj-core --><dependency><groupId>org.assertj</groupId><artifactId>assertj-core</artifactId><version>3.15.0</version><scope>test</scope></dependency>

2. MyResourceのテスト

まずは簡単に、Myrecourceクラスのテストを書いてみます。
私はimportで悩んだのでimport文まで全て載せます。

MyResourceTest.java
packagecom.example;importorg.glassfish.jersey.server.ResourceConfig;importorg.glassfish.jersey.test.JerseyTest;importorg.junit.jupiter.api.AfterEach;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importjavax.ws.rs.core.Application;importjavax.ws.rs.core.Response;importstaticorg.assertj.core.api.Assertions.assertThat;classMyResourceTestextendsJerseyTest{@OverrideprotectedApplicationconfigure(){returnnewResourceConfig(MyResource.class);}@BeforeEach@OverridepublicvoidsetUp()throwsException{super.setUp();}@AfterEach@OverridepublicvoidtearDown()throwsException{super.tearDown();}@TestpublicvoidgetIt(){finalResponseresponse=target("/myresource").request().get();Stringcontent=response.readEntity(String.class);assertThat(content).isEqualTo("Got it!\n\n");}}

テストは基本的に以下のステップで書いていけば良いかと思います。

  • JerseyTestを継承したテストクラスを作る
  • configureメソッドをオーバーライドする
  • @BeforeEach@AfterEachメソッドを作る
    • ここがJUnit5に対応させるための苦肉の策部分になります。JerseyTestクラスでは、JUnit4用の@Before@Afterが使われているのですが、JUnit5ではこれが呼び出されません。なので、わざわざラップしてやっています。面倒な場合は、エクステンションを作ってくださってる方などがいらっしゃるようなので、それを使うのも手かもしれません。
  • テストメソッドを作る
    • target("パス").request().get()でリクエストを投げてレスポンスを受け取る
    • response.readEntity(クラス名)で戻り値を取得する
    • アサーションで結果確認

実行してみましょう。

run_junit.png

ログを見ると、ポート番号が"9998"などで動いているのがわかりますね。

通過したら、残りのテストも書いていきましょう。

3. EmployeeResourceのテスト

MyResourceのテストを参考に書けると思います。

EmployeeResourceTest.java
packagecom.example;importorg.glassfish.jersey.server.ResourceConfig;importorg.glassfish.jersey.test.JerseyTest;importorg.junit.jupiter.api.AfterEach;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importjavax.ws.rs.core.Application;importjavax.ws.rs.core.GenericType;importjavax.ws.rs.core.Response;importjava.util.List;importstaticorg.assertj.core.api.Assertions.assertThat;importstaticorg.junit.jupiter.api.Assertions.*;classEmployeeResourceTestextendsJerseyTest{@OverrideprotectedApplicationconfigure(){returnnewResourceConfig(EmployeeResource.class);}@BeforeEach@OverridepublicvoidsetUp()throwsException{super.setUp();}@AfterEach@OverridepublicvoidtearDown()throwsException{super.tearDown();}@TestvoidgetAll(){finalResponseresponse=target("/employees/all").request().get();List<Employee>content=response.readEntity(newGenericType<>(){});assertThat(content.size()).isEqualTo(5);assertThat(content.get(0)).isEqualToComparingFieldByField(newEmployee(3,"Cupcake"));assertThat(content.get(1)).isEqualToComparingFieldByField(newEmployee(4,"Donuts"));assertThat(content.get(2)).isEqualToComparingFieldByField(newEmployee(5,"Eclair"));assertThat(content.get(3)).isEqualToComparingFieldByField(newEmployee(8,"Froyo"));assertThat(content.get(4)).isEqualToComparingFieldByField(newEmployee(9,"Gingerbread"));}@TestvoidgetEmployee(){Employeeemployee=target("/employees/3").request().get(Employee.class);assertThat(employee).isEqualToComparingFieldByField(newEmployee(3,"Cupcake"));employee=target("/employees/4").request().get(Employee.class);assertThat(employee).isEqualToComparingFieldByField(newEmployee(4,"Donuts"));employee=target("/employees/5").request().get(Employee.class);assertThat(employee).isEqualToComparingFieldByField(newEmployee(5,"Eclair"));employee=target("/employees/8").request().get(Employee.class);assertThat(employee).isEqualToComparingFieldByField(newEmployee(8,"Froyo"));employee=target("/employees/9").request().get(Employee.class);assertThat(employee).isEqualToComparingFieldByField(newEmployee(9,"Gingerbread"));}@Testvoidsearch(){finalList<Employee>content=target("/employees/search").queryParam("name","a").request().get(newGenericType<>(){});assertThat(content.size()).isEqualTo(3);assertThat(content.get(0)).isEqualToComparingFieldByField(newEmployee(3,"Cupcake"));assertThat(content.get(1)).isEqualToComparingFieldByField(newEmployee(5,"Eclair"));assertThat(content.get(2)).isEqualToComparingFieldByField(newEmployee(9,"Gingerbread"));}}

getEmployeeのテストで、

Employeeemployee=target("/employees/3").request().get(Employee.class);

としています。get()に型を指定すると、readEntityをその型で処理した値を直接受け取れます。

また、searchのテストでは、queryParamを使って、クエリーパラメーターを指定しています。

感想

GET系は、たぶん、簡単です。まあ色々ハマりはしましたが。JerseyテストフレームワークがJUnit5に対応していないせいでテストが動かないとか。。。対処法はごく簡単なのに、何時間悩んだことか・・・(泣)

でも、XMLを返そうとしたり、POSTをやろうとしたらもっとハマったので、それを次回にします。
あと、例外系も次回やります。
今は存在しないidを指定して検索すると、500エラーになりますが、これを変えていきます。

参考ページ

いっぱい見たなあ・・・


Viewing all articles
Browse latest Browse all 470

Trending Articles