Save 37% off PRO during our Black Friday Sale! »

Groovy REST - SpringOne2GX 2015 - Guillaume Laforge

Groovy REST - SpringOne2GX 2015 - Guillaume Laforge

137d3908243acfc30e126615d59d4e6d?s=128

Guillaume Laforge

September 15, 2015
Tweet

Transcript

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

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

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

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

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

  7. None
  8. SPRINGONE2GX WASHINGTON, DC Quick intro to REST

  9. ROY FIELDING REST DISSERTATION

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

    architecture
  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
  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
  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
  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…
  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…
  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…
  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
  18. @glaforge Common HTTP Status Codes — 1xx 11

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  41. SPRINGONE2GX WASHINGTON, DC REST — Server-side

  42. @glaforge Solutions for your REST backend • Grails • Ratpack

    • Spring Boot • gServ • Restlet Framework • any other 
 Web framework! 17
  43. None
  44. May the Groovy force be with you!

  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)
  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()
 }
  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
  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'])
 }
 }
 }
 }
  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???
  50. @glaforge Ratpack 1.0 is out! 22

  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" } }
  52. @glaforge Ratpack 24

  53. None
  54. Beep, let’s move to the client side of the REST

    force!
  55. SPRINGONE2GX WASHINGTON, DC REST client-side

  56. SPRINGONE2GX WASHINGTON, DC REST client-side

  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
  58. WEB

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

  60. JAVA

  61. JAVA & GROOVY

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

  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
  64. 403

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

  66. @glaforge Know your GDK! 33

  67. @glaforge new URL(‘…’).text… take 2! 34 'https://starwars.apispark.net/v1/people/1'
 .toURL()
 .getText(requestProperties:
 ['User-Agent':

    'Firefox', 
 'Accept' : 'application/json'])
  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
  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'
  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"
 }
 }
  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
  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
  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"
 }
 }
  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
  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"
 

  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"
 }
 }
  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"
 

  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"
 }
 }
  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
  80. @glaforge Restlet Framework client 42 
 
 
 
 


    
 
 
 
 
 def resource = new ClientResource("${endpoint}/people/1")
 
 
 def representation = resource.get(APPLICATION_JSON)
 
 
 representation.text.contains "Luke Skywalker"
 

  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'
  82. SPRINGONE2GX WASHINGTON, DC Miscellaneous

  83. SPRINGONE2GX WASHINGTON, DC Miscellaneous

  84. @glaforge httpie 45

  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'))
  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')
 }
 }
 }
  87. @glaforge Runscope 48

  88. @glaforge Mockbin 49

  89. @glaforge Httpbin 50

  90. @glaforge Requestb.in 51

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

  93. SPRINGONE2GX WASHINGTON, DC Thanks for your attention!

  94. SPRINGONE2GX WASHINGTON, DC Questions & Answers

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

    Groovy REST! @glaforge