$30 off During Our Annual Pro Sale. View Details »

Groovy REST - SpringOne2GX 2015 - Guillaume Laforge

Groovy REST - SpringOne2GX 2015 - Guillaume Laforge

Guillaume Laforge

September 15, 2015
Tweet

More Decks by Guillaume Laforge

Other Decks in Technology

Transcript

  1. SPRINGONE2GX
    WASHINGTON, DC
    Take a Groovy REST!
    by Guillaume Laforge
    @glaforge

    View Slide

  2. View Slide

  3. Promo Code: ctwspringo2gx
    http://www.manning.com/koenig2/
    GROOVY
    IN ACTION
    2ND EDITION

    View Slide

  4. We know
    about
    APIs!
    http://restlet.com

    View Slide

  5. We know
    about
    APIs!
    http://restlet.com

    View Slide

  6. @glaforge
    APISpark — sign-up!
    4
    http://restlet.com

    View Slide

  7. View Slide

  8. SPRINGONE2GX
    WASHINGTON, DC
    Quick intro to REST

    View Slide

  9. ROY FIELDING
    REST
    DISSERTATION

    View Slide

  10. ROY FIELDING
    REST
    DISSERTATION
    Principled design
    of the modern
    Web architecture

    View Slide

  11. @glaforge
    Representational State Transfer
    Architectural properties
    • Performance
    • Scalability
    • Simplicity
    • Modifiability
    • Visibility
    • Portability
    • Reliability
    Architectural constraints
    • Client-server
    • Stateless
    • Cacheable
    • Layered system
    • Code on demand (optional)
    • Uniform interface
    8

    View Slide

  12. @glaforge
    REST — Uniform interface
    • Identification of resources
    • Manipulation of resources 

    through representations
    • Self-descriptive messages
    • HATEOAS 

    (Hypermedia As The Engine 

    Of Application State)
    9

    View Slide

  13. @glaforge
    REST — Uniform interface
    • Identification of resources
    • Manipulation of resources 

    through representations
    • Self-descriptive messages
    • HATEOAS 

    (Hypermedia As The Engine 

    Of Application State)
    9
    Resource as URIs
    http://api.co/cars/123

    View Slide

  14. @glaforge
    REST — Uniform interface
    • Identification of resources
    • Manipulation of resources 

    through representations
    • Self-descriptive messages
    • HATEOAS 

    (Hypermedia As The Engine 

    Of Application State)
    9
    Resource as URIs
    http://api.co/cars/123
    JSON, XML…

    View Slide

  15. @glaforge
    REST — Uniform interface
    • Identification of resources
    • Manipulation of resources 

    through representations
    • Self-descriptive messages
    • HATEOAS 

    (Hypermedia As The Engine 

    Of Application State)
    9
    Resource as URIs
    http://api.co/cars/123
    JSON, XML…
    HTTP GET, POST, PUT, DELETE
    media types, cacheability…

    View Slide

  16. @glaforge
    REST — Uniform interface
    • Identification of resources
    • Manipulation of resources 

    through representations
    • Self-descriptive messages
    • HATEOAS 

    (Hypermedia As The Engine 

    Of Application State)
    9
    Resource as URIs
    http://api.co/cars/123
    JSON, XML…
    HTTP GET, POST, PUT, DELETE
    media types, cacheability…
    Hypermedia APIs
    HAL, JSON-LD, Siren…

    View Slide

  17. @glaforge
    HTTP methods / URIs for collection/item
    10
    GET
    POST
    PUT
    DELETE
    http://api.co/v2/cars/ http://api.co/v2/cars/1234
    List all the cars Retrieve an individual car
    Create a new car
    Replace the entire collection
    with a whole new list of cars
    Replace or create
    an individual car
    Delete all the cars Delete an individual car

    View Slide

  18. @glaforge
    Common HTTP Status Codes — 1xx
    11

    View Slide

  19. @glaforge
    Common HTTP Status Codes — 2xx
    12

    View Slide

  20. @glaforge
    Common HTTP Status Codes — 2xx
    12

    View Slide

  21. @glaforge
    Common HTTP Status Codes — 2xx
    12

    View Slide

  22. @glaforge
    Common HTTP Status Codes — 2xx
    12

    View Slide

  23. @glaforge
    Common HTTP Status Codes — 2xx
    12

    View Slide

  24. @glaforge
    Common HTTP Status Codes — 2xx
    12

    View Slide

  25. @glaforge
    Common HTTP Status Codes — 3xx
    13

    View Slide

  26. @glaforge
    Common HTTP Status Codes — 3xx
    13

    View Slide

  27. @glaforge
    Common HTTP Status Codes — 3xx
    13

    View Slide

  28. @glaforge
    Common HTTP Status Codes — 3xx
    13

    View Slide

  29. @glaforge
    Common HTTP Status Codes — 3xx
    13

    View Slide

  30. @glaforge
    Common HTTP Status Codes — 3xx
    13

    View Slide

  31. @glaforge
    Common HTTP Status Codes — 4xx
    14

    View Slide

  32. @glaforge
    Common HTTP Status Codes — 4xx
    14

    View Slide

  33. @glaforge
    Common HTTP Status Codes — 4xx
    14

    View Slide

  34. @glaforge
    Common HTTP Status Codes — 4xx
    14

    View Slide

  35. @glaforge
    Common HTTP Status Codes — 4xx
    14

    View Slide

  36. @glaforge
    Common HTTP Status Codes — 4xx
    14

    View Slide

  37. @glaforge
    Common HTTP Status Codes — 5xx
    15

    View Slide

  38. @glaforge
    Common HTTP Status Codes — 5xx
    15

    View Slide

  39. @glaforge
    Common HTTP Status Codes — 5xx
    15

    View Slide

  40. @glaforge
    Common HTTP Status Codes — 5xx
    15

    View Slide

  41. SPRINGONE2GX
    WASHINGTON, DC
    REST — Server-side

    View Slide

  42. @glaforge
    Solutions for your REST backend
    • Grails
    • Ratpack
    • Spring Boot
    • gServ
    • Restlet Framework
    • any other 

    Web framework!
    17

    View Slide

  43. View Slide

  44. May the Groovy
    force be with you!

    View Slide

  45. @glaforge
    gServ
    19
    def gserv = new GServ()


    def bkResource = gserv.resource("/people") {

    get ":id", { id ->

    def person = personService.get( id )

    writeJson person

    }


    get "/", { ->

    def persons = personService.all()

    responseHeader "content-type", "application/json"

    writeJSON persons

    }

    }


    gserv.http {

    static_root '/public/webapp'

    get '/faq', file("App.faq.html")


    }.start(8080)

    View Slide

  46. @glaforge
    Restlet Framework
    20
    @GrabResolver('http://maven.restlet.com')

    @Grab('org.restlet.jse:org.restlet:2.3.2')

    import org.restlet.*

    import org.restlet.resource.*

    import org.restlet.data.*

    import org.restlet.routing.*

    import static org.restlet.data.MediaType.*


    class PeopleResource extends ServerResource {

    @Get('json')

    String toString() { "{'name': 'Luke Skywalker’}" }

    }


    new Server(Protocol.HTTP, 8183, PeopleResource).with { server ->

    start()

    new Router(server.context).attach("/people/{user}", PeopleResource)


    assert new ClientResource('http://localhost:8183/people/1')

    .get(APPLICATION_JSON).text.contains('Luke Skywalker')


    stop()

    }

    View Slide

  47. @glaforge
    Restlet Framework
    20
    @GrabResolver('http://maven.restlet.com')

    @Grab('org.restlet.jse:org.restlet:2.3.2')

    import org.restlet.*

    import org.restlet.resource.*

    import org.restlet.data.*

    import org.restlet.routing.*

    import static org.restlet.data.MediaType.*


    class PeopleResource extends ServerResource {

    @Get('json')

    String toString() { "{'name': 'Luke Skywalker’}" }

    }


    new Server(Protocol.HTTP, 8183, PeopleResource).with { server ->

    start()

    new Router(server.context).attach("/people/{user}", PeopleResource)


    assert new ClientResource('http://localhost:8183/people/1')

    .get(APPLICATION_JSON).text.contains('Luke Skywalker')


    stop()

    }
    Starts in
    milliseconds

    View Slide

  48. @glaforge
    Ratpack
    21
    @Grab('io.ratpack:ratpack-groovy:0.9.17')

    import static ratpack.groovy.Groovy.ratpack

    import static groovy.json.JsonOutput.toJson


    ratpack {

    handlers {

    get("/people/:id") {

    respond byContent.json {

    response.send toJson(

    [name: 'Luke Skywalker'])

    }

    }

    }

    }

    View Slide

  49. @glaforge
    Ratpack
    21
    @Grab('io.ratpack:ratpack-groovy:0.9.17')

    import static ratpack.groovy.Groovy.ratpack

    import static groovy.json.JsonOutput.toJson


    ratpack {

    handlers {

    get("/people/:id") {

    respond byContent.json {

    response.send toJson(

    [name: 'Luke Skywalker'])

    }

    }

    }

    }
    Booh! Ratpack
    0.9.17???

    View Slide

  50. @glaforge
    Ratpack 1.0 is out!
    22

    View Slide

  51. @glaforge
    Ratpack
    23
    @Grab(’org.slf4j:slf4j-simple:1.7.12’)
    @Grab(’io.ratpack:ratpack-groovy:1.0.0’)
    import static ratpack.groovy.Groovy.ratpack
    import static ratpack.groovy.Groovy.htmlBuilder as h
    import static ratpack.jackson.Jackson.json as j
    ratpack {
    handlers {
    get { render "foo"}
    get("people/:id") {
    byContent {
    json {
    render j(name: "Luke Skywalker")
    }
    html {
    render h {
    p "Luke Skywalker"
    }
    }

    View Slide

  52. @glaforge
    Ratpack
    24

    View Slide

  53. View Slide

  54. Beep, let’s move to
    the client side of the
    REST force!

    View Slide

  55. SPRINGONE2GX
    WASHINGTON, DC
    REST client-side

    View Slide

  56. SPRINGONE2GX
    WASHINGTON, DC
    REST client-side

    View Slide

  57. @glaforge
    REST clients — Web, Java, Android
    Web browser

    (client-side JavaScript)
    • Raw AJAX, jQuery
    • Angular’s http request,
    Restangular
    • Restful.js
    + GrooScript! :-)
    Java-based app 

    (Android or Java backend)
    • Groovy wslite
    • Groovy HTTPBuilder
    • RetroFit / OkHttp
    • Restlet Framework
    27

    View Slide

  58. WEB

    View Slide

  59. @glaforge
    Restful.js (+ GrooScript ?)
    29

    View Slide

  60. JAVA

    View Slide

  61. JAVA
    & GROOVY

    View Slide

  62. @glaforge
    new URL(‘…’).text… or not?
    31
    'https://starwars.apispark.net/v1/people/1'.toURL().text

    View Slide

  63. @glaforge
    new URL(‘…’).text… or not?
    31
    'https://starwars.apispark.net/v1/people/1'.toURL().text
    getText() will do
    an HTTP GET
    on the URL

    View Slide

  64. 403

    View Slide

  65. 403
    The API doesn’t
    accept a Java user
    agent

    View Slide

  66. @glaforge
    Know your GDK!
    33

    View Slide

  67. @glaforge
    new URL(‘…’).text… take 2!
    34
    'https://starwars.apispark.net/v1/people/1'

    .toURL()

    .getText(requestProperties:

    ['User-Agent': 'Firefox', 

    'Accept' : 'application/json'])

    View Slide

  68. @glaforge
    new URL(‘…’).text… take 2!
    34
    'https://starwars.apispark.net/v1/people/1'

    .toURL()

    .getText(requestProperties:

    ['User-Agent': 'Firefox', 

    'Accept' : 'application/json'])
    Let’s ask for
    JSON payload

    View Slide

  69. @glaforge
    Now JSON-parsed…
    35
    import groovy.json.*


    assert new JsonSlurper().parseText(

    'https://starwars.apispark.net/v1/people/1'

    .toURL()

    .getText(requestProperties:

    ['User-Agent': 'Firefox', 

    'Accept': 'application/json'])

    ).name == 'Luke Skywalker'

    View Slide

  70. @glaforge
    …and spockified!
    36
    class StarWars extends Specification {

    def "retrieve Luke"() {

    given: "a URL to the resource"

    def url = 'https://starwars.apispark.net/v1'.toURL()


    when: "I retrieve and parse the people/1"

    def parsed = url.getText(requestProperties:

    ['User-Agent': 'Firefox', 

    'Accept': 'application/json'])


    then: "I expect to get Luke"

    parsed.contains "Luke Skywalker"

    }

    }

    View Slide

  71. @glaforge
    …and spockified!
    36
    class StarWars extends Specification {

    def "retrieve Luke"() {

    given: "a URL to the resource"

    def url = 'https://starwars.apispark.net/v1'.toURL()


    when: "I retrieve and parse the people/1"

    def parsed = url.getText(requestProperties:

    ['User-Agent': 'Firefox', 

    'Accept': 'application/json'])


    then: "I expect to get Luke"

    parsed.contains "Luke Skywalker"

    }

    }
    Neat for GET,
    but not other
    methods
    supported

    View Slide

  72. @glaforge
    …and spockified!
    36
    class StarWars extends Specification {

    def "retrieve Luke"() {

    given: "a URL to the resource"

    def url = 'https://starwars.apispark.net/v1'.toURL()


    when: "I retrieve and parse the people/1"

    def parsed = url.getText(requestProperties:

    ['User-Agent': 'Firefox', 

    'Accept': 'application/json'])


    then: "I expect to get Luke"

    parsed.contains "Luke Skywalker"

    }

    }
    Neat for GET,
    but not other
    methods
    supported
    Raw text, no
    JSON parsing

    View Slide

  73. @glaforge
    Groovy wslite
    37
    @Grab('com.github.groovy-wslite:groovy-wslite:1.1.0')

    import wslite.rest.*

    import wslite.http.*


    class StarWars3 extends Specification {

    static endpoint = 'https://starwars.apispark.net/v1'


    def "retrieve Luke"() {

    given: "a connection to the API"

    def client = new RESTClient(endpoint)


    when: "I retrieve the people/1"

    def result = client.get(path: '/people/1', headers:

    ['User-Agent': 'Firefox', 'Accept': 'application/json'])


    then: "I expect to get Luke"

    result.json.name == "Luke Skywalker"

    }

    }

    View Slide

  74. @glaforge
    Groovy wslite
    37
    @Grab('com.github.groovy-wslite:groovy-wslite:1.1.0')

    import wslite.rest.*

    import wslite.http.*


    class StarWars3 extends Specification {

    static endpoint = 'https://starwars.apispark.net/v1'


    def "retrieve Luke"() {

    given: "a connection to the API"

    def client = new RESTClient(endpoint)


    when: "I retrieve the people/1"

    def result = client.get(path: '/people/1', headers:

    ['User-Agent': 'Firefox', 'Accept': 'application/json'])


    then: "I expect to get Luke"

    result.json.name == "Luke Skywalker"

    }

    }
    Transparent
    JSON parsing

    View Slide

  75. @glaforge
    Groovy wslite
    38









    def client = new RESTClient(endpoint)



    def result = client.get(path: '/people/1', headers:

    ['User-Agent': 'Firefox', 'Accept': 'application/json'])



    result.json.name == "Luke Skywalker"


    View Slide

  76. @glaforge
    Groovy HTTPBuilder
    39
    @Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7.1')

    import groovyx.net.http.RESTClient

    import static groovyx.net.http.ContentType.JSON


    class StarWars4 extends Specification {

    static endpoint = 'https://starwars.apispark.net/v1/'


    def "retrieve Luke"() {

    given: "a connection to the API"

    def client = new RESTClient(endpoint)


    when: "I retrieve the people/1"

    def result = client.get(path: ‘people/1', contentType: JSON,

    headers: ['User-Agent': 'Firefox'])


    then: "I expect to get Luke"

    result.data.name == "Luke Skywalker"

    }

    }

    View Slide

  77. @glaforge
    Groovy HTTPBuilder
    40









    def client = new RESTClient(endpoint)



    def result = client.get(path: ‘people/1', contentType: JSON,

    headers: ['User-Agent': 'Firefox'])



    result.data.name == "Luke Skywalker"


    View Slide

  78. @glaforge
    Restlet Framework client
    41
    @GrabResolver('http://maven.restlet.com')

    @Grab('org.restlet.jse:org.restlet:2.3.2')

    import org.restlet.resource.*

    import static org.restlet.data.MediaType.*


    class StarWars extends Specification {

    static endpoint = 'https://starwars.apispark.net/v1'


    def "retrieve Luke"() {

    given: "I create a people resource"

    def resource = new ClientResource("${endpoint}/people/1")


    when: "I retrieve the resource"

    def representation = resource.get(APPLICATION_JSON)


    then: "I expect that resource to be Luke!"

    representation.text.contains "Luke Skywalker"

    }

    }

    View Slide

  79. @glaforge
    Restlet Framework client
    41
    @GrabResolver('http://maven.restlet.com')

    @Grab('org.restlet.jse:org.restlet:2.3.2')

    import org.restlet.resource.*

    import static org.restlet.data.MediaType.*


    class StarWars extends Specification {

    static endpoint = 'https://starwars.apispark.net/v1'


    def "retrieve Luke"() {

    given: "I create a people resource"

    def resource = new ClientResource("${endpoint}/people/1")


    when: "I retrieve the resource"

    def representation = resource.get(APPLICATION_JSON)


    then: "I expect that resource to be Luke!"

    representation.text.contains "Luke Skywalker"

    }

    }
    Raw text, no
    JSON parsing

    View Slide

  80. @glaforge
    Restlet Framework client
    42










    def resource = new ClientResource("${endpoint}/people/1")



    def representation = resource.get(APPLICATION_JSON)



    representation.text.contains "Luke Skywalker"


    View Slide

  81. @glaforge
    RetroFit
    43
    @Grab('com.squareup.retrofit:retrofit:1.9.0')

    import retrofit.*

    import retrofit.http.*


    interface StarWarsService {

    @Headers(["Accept: application/json",

    "User-Agent: Firefox"])

    @Get('/people/{id}')

    People retrieve(@Path('id') String id)

    }


    class People { String name }


    def restAdapter = new RestAdapter.Builder()

    .setEndpoint('https://starwars.apispark.net/v1/')
    .build()


    def service = restAdapter.create(StarWarsService)

    assert service.retrieve('1').name == 'Luke Skywalker'

    View Slide

  82. SPRINGONE2GX
    WASHINGTON, DC
    Miscellaneous

    View Slide

  83. SPRINGONE2GX
    WASHINGTON, DC
    Miscellaneous

    View Slide

  84. @glaforge
    httpie
    45

    View Slide

  85. @glaforge
    REST-assured
    46
    @Grab('com.jayway.restassured:rest-assured:2.4.1')

    import static com.jayway.restassured.RestAssured.*

    import static org.hamcrest.Matchers.*


    when().

    get('https://starwars.apispark.net/v1/people/1').

    then().

    statusCode(200).

    body('name', equalTo('Luke Skywalker')).

    body('gender', equalTo('male'))

    View Slide

  86. @glaforge
    REST-assured — a Spock approach
    47
    @Grab('org.spockframework:spock-core:1.0-groovy-2.4')

    import spock.lang.*


    @Grab('com.jayway.restassured:rest-assured:2.4.1')

    import static com.jayway.restassured.RestAssured.*

    import static org.hamcrest.Matchers.*


    class starwars_rest_assured_spock extends Specification {

    def "rest assured and spock"() {

    expect:

    get('https://starwars.apispark.net/v1/people/1').then().with {

    statusCode 200

    body 'name', equalTo('Luke Skywalker')

    }

    }

    }

    View Slide

  87. @glaforge
    Runscope
    48

    View Slide

  88. @glaforge
    Mockbin
    49

    View Slide

  89. @glaforge
    Httpbin
    50

    View Slide

  90. @glaforge
    Requestb.in
    51

    View Slide

  91. View Slide

  92. Arg! The Groovy REST
    force is too strong!

    View Slide

  93. SPRINGONE2GX
    WASHINGTON, DC
    Thanks for your attention!

    View Slide

  94. SPRINGONE2GX
    WASHINGTON, DC
    Questions & Answers

    View Slide

  95. @glaforge 55
    Help us move Groovy forward!
    Thanks!
    Take a Groovy REST!
    @glaforge

    View Slide