Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Groovy Rest — GR8Conf Europe 2015

Groovy Rest — GR8Conf Europe 2015

Client libraries, Web frameworks, to build and interact with Restful Web APIs from Groovy

Guillaume Laforge

June 04, 2015
Tweet

More Decks by Guillaume Laforge

Other Decks in Technology

Transcript

  1. Take a
    Groovy Rest!
    Guillaume Laforge
    @glaforge
    Restlet — the Web API platform
    Groovy project team

    View full-size slide

  2. We know
    about
    APIs!

    View full-size slide

  3. 3
    APISpark — sign-up!

    View full-size slide

  4. GR8Conf Promo Code: cfgr8eu39
    http://www.manning.com/koenig2/
    GROOVY
    IN ACTION
    2ND EDITION

    View full-size slide

  5. Quick intro to REST

    View full-size slide

  6. ROY FIELDING
    REST
    DISSERTATION

    View full-size slide

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

    View full-size slide

  8. 8
    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

    View full-size slide

  9. 9
    REST — Uniform interface
    • Identification of resources
    • Manipulation of resources 

    through representations
    • Self-descriptive messages
    • HATEOAS 

    (Hypermedia As The Engine 

    Of Application State)

    View full-size slide

  10. 9
    REST — Uniform interface
    • Identification of resources
    • Manipulation of resources 

    through representations
    • Self-descriptive messages
    • HATEOAS 

    (Hypermedia As The Engine 

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

    View full-size slide

  11. 9
    REST — Uniform interface
    • Identification of resources
    • Manipulation of resources 

    through representations
    • Self-descriptive messages
    • HATEOAS 

    (Hypermedia As The Engine 

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

    View full-size slide

  12. 9
    REST — Uniform interface
    • Identification of resources
    • Manipulation of resources 

    through representations
    • Self-descriptive messages
    • HATEOAS 

    (Hypermedia As The Engine 

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

    View full-size slide

  13. 9
    REST — Uniform interface
    • Identification of resources
    • Manipulation of resources 

    through representations
    • Self-descriptive messages
    • HATEOAS 

    (Hypermedia As The Engine 

    Of Application State)
    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 full-size slide

  14. 10
    HTTP methods / URIs for collection/item
    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 full-size slide

  15. 11
    Common HTTP Status Codes — 1xx

    View full-size slide

  16. 12
    Common HTTP Status Codes — 2xx

    View full-size slide

  17. 13
    Common HTTP Status Codes — 3xx

    View full-size slide

  18. 14
    Common HTTP Status Codes — 4xx

    View full-size slide

  19. 15
    Common HTTP Status Codes — 5xx

    View full-size slide

  20. REST — Server-side

    View full-size slide

  21. 17
    Solutions for your REST backend
    • Grails
    • Ratpack
    • Spring Boot
    • gServ
    • Restlet Framework
    • any other Web framework!

    View full-size slide

  22. May the Groovy
    force be with you!

    View full-size slide

  23. 19
    gServ def gserv = new GServ()


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

    get "/faq", file("BooksFaq.html")


    get ":id", { id ->

    def book = bookService.get( id )

    writeJson book

    }


    get "/", { ->

    def books = bookService.allBooks()

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

    writeJSON books

    }

    }


    gserv.http {

    static_root '/public/webapp'

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


    }.start(8080)

    View full-size slide

  24. 20
    Ratpack
    @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 full-size slide

  25. 21
    Restlet Framework
    @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 full-size slide

  26. 21
    Restlet Framework
    @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 full-size slide

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

    View full-size slide

  28. REST client-side

    View full-size slide

  29. 24
    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

    View full-size slide

  30. 26
    Restful.js (+ GrooScript ?)

    View full-size slide

  31. JAVA
    & GROOVY

    View full-size slide

  32. 28
    new URL(‘…’).text… or not?
    'https://starwars.apispark.net/v1/people/1'.toURL().text

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  35. 30
    Know your GDK!

    View full-size slide

  36. 31
    new URL(‘…’).text… take 2!
    'https://starwars.apispark.net/v1/people/1'

    .toURL()

    .getText(requestProperties:

    ['User-Agent': 'Firefox', 

    'Accept' : 'application/json'])

    View full-size slide

  37. 31
    new URL(‘…’).text… take 2!
    'https://starwars.apispark.net/v1/people/1'

    .toURL()

    .getText(requestProperties:

    ['User-Agent': 'Firefox', 

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

    View full-size slide

  38. 32
    Now JSON-parsed…
    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 full-size slide

  39. 33
    …and spockified!
    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 full-size slide

  40. 33
    …and spockified!
    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 full-size slide

  41. 33
    …and spockified!
    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 full-size slide

  42. 34
    Groovy wslite
    @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 full-size slide

  43. 34
    Groovy wslite
    @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 full-size slide

  44. 35
    Groovy wslite









    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 full-size slide

  45. 36
    Groovy HTTPBuilder
    @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 full-size slide

  46. 37
    Groovy HTTPBuilder









    def client = new RESTClient(endpoint)



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

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



    result.data.name == "Luke Skywalker"


    View full-size slide

  47. 38
    Restlet Framework client
    @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 full-size slide

  48. 38
    Restlet Framework client
    @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 full-size slide

  49. 39
    Restlet Framework client










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



    def representation = resource.get(APPLICATION_JSON)



    representation.text.contains "Luke Skywalker"


    View full-size slide

  50. 40
    RetroFit
    @Grab('com.squareup.retrofit:retrofit:1.9.0')

    import retrofit.*

    import retrofit.http.*


    interface StarWarsService {

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

    "User-Agent: Firefox"])

    @Headers('/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 full-size slide

  51. Miscellaneous

    View full-size slide

  52. 43
    REST-assured
    @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 full-size slide

  53. 44
    REST-assured — a Spock approach
    @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 full-size slide

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

    View full-size slide

  55. Thanks for your attention!

    View full-size slide

  56. Questions & Answers

    View full-size slide