3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

G*Advent Calendar(Groovy,Grails,Gradle,Spock...) Advent Calendar 2015Advent Calendar 2015

Day 22

GroovyでJSON返すAPIサーバを作る(Embedded Jetty)

Last updated at Posted at 2015-12-06

こちらのAPIサーバの記事に感激して、JSON返すバージョンにしてみた。
http://qiita.com/kimromi/items/1c797b1f53fbc1412c8d

一旦分かってしまえばお手軽なのがGroovyの良いところだと思う。

方針

  • GroovyでAPIサーバを作る
  • 設定ファイルは書かかない
  • 戻り値はJSONにする

コード

サーバ

追加したのはjersey-jsonのインストールと、
com.sun.jersey.api.json.POJOMappingFeatureの設定。

server.groovy
@Grab('org.eclipse.jetty.aggregate:jetty-all:9.2.6.v20141205')
@Grab('com.sun.jersey:jersey-server:1.18.3')
@Grab('com.sun.jersey:jersey-servlet:1.18.3')
@Grab('com.sun.jersey:jersey-core:1.18.3')
@Grab('com.sun.jersey:jersey-json:1.18.3')

import com.sun.jersey.spi.container.servlet.ServletContainer

import org.eclipse.jetty.server.handler.ContextHandler
import org.eclipse.jetty.server.handler.HandlerList
import org.eclipse.jetty.server.handler.ResourceHandler
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.eclipse.jetty.server.Server


/*----- jersey -----*/
//JAX-RSのresource、providerに含めるクラスを指定
def ResourceClass = [
    HelloResource.class,
]
def servletContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
def servletHolder = new ServletHolder(ServletContainer.class);

//サーブレットとして動作させるクラス名を指定("HogeClass;FugaClass;…"の形)
servletHolder.setInitParameters([
    "com.sun.jersey.config.property.resourceConfigClass": "com.sun.jersey.api.core.ClassNamesResourceConfig",
    "com.sun.jersey.config.property.classnames": ResourceClass.collect{it.getName()}.join(";"),
    "com.sun.jersey.api.json.POJOMappingFeature": "true"
])
servletContext.setContextPath("/api")        //URL設定
servletContext.addServlet(servletHolder, "/*")

/*----- サーバ起動 -----*/
def server = new Server(8888);               //ポート設定
//コンテキスト定義をサーバにハンドル
def handlerList = new HandlerList()
handlerList.setHandlers(servletContext)
server.setHandler(handlerList)

println "APIサーバーを起動します"
server.start()                               //サーバ起動

/*----- 終了時 -----*/
addShutdownHook {
  println "APIサーバーを停止します"
  server.stop()
}

API

やっていること

  • APIに対応するメソッドの戻り値をデータクラス(DTO)の型にする
  • MediaTypeにAPPLICATION_JSONを指定する

これで上手く変換できる。

HelloResource.groovy
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.MediaType

//TODOこれ不要では?★
import HelloResultDto

@Path("/")
class HelloResource {

    @GET
    @Path("/hello")
    String getHello() {
        return "hello!"
    }

    @GET
    @Path("/helloJson")
    @Produces(MediaType.APPLICATION_JSON)
    public HelloResultDto helloJson() {
        HelloResultDto dto = new HelloResultDto()
        dto.name1 = "AAA"
        dto.name2 = "BBB"
        dto.name3 = "CCC"
        dto.name4 = "DDD"
        return dto
    }

    @GET
    @Path("/helloJson2")
    @Produces(MediaType.APPLICATION_JSON)
    public HelloResultDto helloJson2() {
        return new HelloResultDto("AAA", "BBB", "CCC", "DDD")
    }

    // JSONのリストを返す
    @GET
    @Path("/helloJsonList")
    @Produces(MediaType.APPLICATION_JSON)
    public List<HelloResultDto> helloJsonList() {
        def jsonList = new ArrayList()
        jsonList.add( new HelloResultDto("AAA", "BBB", "CCC", "DDD") )
        jsonList.add( new HelloResultDto("EEE", "FFF", "GGG", "HHH") )
        return jsonList
    }

    @GET
    @Path("/error")
    String getError() {
        throw new HelloException()
    }

    @GET
    @Path("/error2")
    String getError2() {
        throw new HelloException("P", "O", "I", "!")
    }
}

JSON戻り値用のDTOクラス

HelloResultDto.groovy
import javax.xml.bind.annotation.XmlRootElement

@XmlRootElement
class HelloResultDto {
    String name1
    String name2
    String name3
    String name4

    //引数なしでもnewできるようにする
    HelloResultDto() {
    }

    HelloResultDto(name1, name2, name3, name4) {
        this.name1 = name1
        this.name2 = name2
        this.name3 = name3
        this.name4 = name4
    }
}

エラー処理用例外クラス

HelloException.groovy
import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.ResponseBuilder
import javax.ws.rs.core.Response.Status
import javax.ws.rs.WebApplicationException

class HelloException extends WebApplicationException {
    HelloException() {
        //ステータス404を入れる
        super(Response
            .status(Status.NOT_FOUND)
            .build()
        )
    }

    HelloException(name1, name2, name3, name4) {
        super(Response
            //ステータス400を入れる
            .status(Status.BAD_REQUEST)
            .entity(new HelloResultDto(name1, name2, name3, name4))
            //JSONとして返す
            .type("application/json")
            .build()
        )
    }
}

動作確認

サーバ起動

groovy server.groovy

HTTPアクセス 正常系

http://localhost:8888/api/helloJson

出力

{"name1":"AAA","name2":"BBB","name3":"CCC","name4":"DDD"}

正常系その2

http://localhost:8888/api/helloJson

出力

さっきのと同じ

{"name1":"AAA","name2":"BBB","name3":"CCC","name4":"DDD"}

異常系

http://localhost:8888/api/error

出力

何も出ない。
Chromeの開発者ツールで見ると、コンソールログからHTTPステータスが404に設定できていることが分かる。

Failed to load resource: the server responded with a status of 404 (Not Found)

異常系その2

今度は応答JSON有り。

http://localhost:8888/api/error2

出力

Chromeの開発者ツールで見ると、コンソールログからHTTPステータスが400に設定できていることが分かる。

{"name1":"P","name2":"O","name3":"I","name4":"!"}

参考

Embedded JettyをGroovyで使う話を参考にしました。
http://qiita.com/kimromi/items/1c797b1f53fbc1412c8d

JerseyでJSON返す話を参考にしました。
http://qiita.com/mmmm/items/d0e78daa2cb9fab51ee9

JerseyでJSON返す時のマッピングエラーの解決策の参考にしました。
http://stackoverflow.com/questions/13108161/a-message-body-writer-for-java-class-not-found

例外クラスの書き方の参考にしました。
(Example 7.5. Application specific exception implementation)
https://jersey.java.net/documentation/latest/representations.html#d0e6632

可変パスの話を追記したい
http://www.onestepbeyond.jp/2013/01/jax-rsjersey-2.html

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?