Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Take a Groovy REST

1,842 views

Published on

Guillaume Laforge, Product Ninja & Advocate at Restlet and Chair of the Apache Groovy PMC, presented about how to use Groovy for developing and consuming REST Web APIs at the JavaOne 2015 conference

Published in: Technology
  • Be the first to comment

Take a Groovy REST

  1. 1. Take a Groovy REST! by Guillaume Laforge @glaforge
  2. 2. Promo Code: ctwjavaone http://www.manning.com/koenig2/ GROOVY IN ACTION 2ND EDITION
  3. 3. We know about APIs! http://restlet.com
  4. 4. We know about APIs! http://restlet.com
  5. 5. @glaforge APISpark — sign-up! 4 http://restlet.com
  6. 6. Quick intro to REST
  7. 7. ROY FIELDING RESTDISSERTATION
  8. 8. ROY FIELDING RESTDISSERTATION Principled design of the modern Web architecture
  9. 9. @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
  10. 10. @glaforge REST — Uniform interface • Identification of resources • Manipulation of resources 
 through representations • Self-descriptive messages • HATEOAS 
 (Hypermedia As The Engine 
 Of Application State) 9
  11. 11. @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
  12. 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 Resource as URIs http://api.co/cars/123 JSON, XML…
  13. 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 JSON, XML… HTTP GET, POST, PUT, DELETE media types, cacheability…
  14. 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… HTTP GET, POST, PUT, DELETE media types, cacheability… Hypermedia APIs HAL, JSON-LD, Siren…
  15. 15. @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
  16. 16. @glaforge Common HTTP Status Codes — 1xx 11
  17. 17. @glaforge Common HTTP Status Codes — 2xx 12
  18. 18. @glaforge Common HTTP Status Codes — 2xx 12
  19. 19. @glaforge Common HTTP Status Codes — 2xx 12
  20. 20. @glaforge Common HTTP Status Codes — 2xx 12
  21. 21. @glaforge Common HTTP Status Codes — 2xx 12
  22. 22. @glaforge Common HTTP Status Codes — 2xx 12
  23. 23. @glaforge Common HTTP Status Codes — 3xx 13
  24. 24. @glaforge Common HTTP Status Codes — 3xx 13
  25. 25. @glaforge Common HTTP Status Codes — 3xx 13
  26. 26. @glaforge Common HTTP Status Codes — 3xx 13
  27. 27. @glaforge Common HTTP Status Codes — 3xx 13
  28. 28. @glaforge Common HTTP Status Codes — 3xx 13
  29. 29. @glaforge Common HTTP Status Codes — 4xx 14
  30. 30. @glaforge Common HTTP Status Codes — 4xx 14
  31. 31. @glaforge Common HTTP Status Codes — 4xx 14
  32. 32. @glaforge Common HTTP Status Codes — 4xx 14
  33. 33. @glaforge Common HTTP Status Codes — 4xx 14
  34. 34. @glaforge Common HTTP Status Codes — 4xx 14
  35. 35. @glaforge Common HTTP Status Codes — 5xx 15
  36. 36. @glaforge Common HTTP Status Codes — 5xx 15
  37. 37. @glaforge Common HTTP Status Codes — 5xx 15
  38. 38. @glaforge Common HTTP Status Codes — 5xx 15
  39. 39. REST — Server-side
  40. 40. @glaforge Solutions for your REST backend • Grails • Ratpack • Spring Boot • gServ • Restlet Framework • any other 
 Web framework! 17
  41. 41. May the Groovy force be with you!
  42. 42. @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)
  43. 43. @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()
 }
  44. 44. @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
  45. 45. @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'])
 }
 }
 }
 }
  46. 46. @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???
  47. 47. @glaforge Ratpack 1.0 is out! 22
  48. 48. @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" } }
  49. 49. @glaforge Ratpack 24
  50. 50. Beep, let’s move to the client side of the REST force!
  51. 51. REST client-side
  52. 52. REST client-side
  53. 53. @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
  54. 54. WEB
  55. 55. @glaforge Restful.js (+ GrooScript ?) 29
  56. 56. JAVA
  57. 57. JAVA& GROOVY
  58. 58. @glaforge new URL(‘…’).text… or not? 31 'https://starwars.apispark.net/v1/people/1'.toURL().text
  59. 59. @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
  60. 60. 403
  61. 61. 403The API doesn’t accept a Java user agent
  62. 62. @glaforge Know your GDK! 33
  63. 63. @glaforge new URL(‘…’).text… take 2! 34 'https://starwars.apispark.net/v1/people/1'
 .toURL()
 .getText(requestProperties:
 ['User-Agent': 'Firefox', 
 'Accept' : 'application/json'])
  64. 64. @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
  65. 65. @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'
  66. 66. @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"
 }
 }
  67. 67. @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
  68. 68. @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
  69. 69. @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"
 }
 }
  70. 70. @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
  71. 71. @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"
 

  72. 72. @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"
 }
 }
  73. 73. @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"
 

  74. 74. @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"
 }
 }
  75. 75. @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
  76. 76. @glaforge Restlet Framework client 42 
 
 
 
 
 
 
 
 
 
 def resource = new ClientResource("${endpoint}/people/1")
 
 
 def representation = resource.get(APPLICATION_JSON)
 
 
 representation.text.contains "Luke Skywalker"
 

  77. 77. @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'
  78. 78. Miscellaneous
  79. 79. Miscellaneous
  80. 80. @glaforge DHC by Restlet 45
  81. 81. @glaforge DHC by Restlet 45 http://bit.ly/dhctuto
  82. 82. @glaforge httpie 46
  83. 83. @glaforge REST-assured 47 @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'))
  84. 84. @glaforge REST-assured — a Spock approach 48 @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')
 }
 }
 }
  85. 85. @glaforge Runscope 49
  86. 86. @glaforge Mockbin 50
  87. 87. @glaforge Httpbin 51
  88. 88. @glaforge Requestb.in 52
  89. 89. Arg! The Groovy REST force is too strong!
  90. 90. Thanks for your attention!
  91. 91. Questions & Answers
  92. 92. @glaforge 56 Help us move Groovy forward! Thanks! Take a Groovy REST! @glaforge

×