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

2,448 views

Published on

Recorded at SpringOne2GX 2015
Presenter: Guillame Laforge
Core Groovy Track
Web APIs are everywhere, at every corner of the Web! To be "hip", let's stay at Rest. For even more hype, what can we do with the Groovy programming language and Restful Web APIs? In this session, Guillaume Laforge will talk about APIs, how do Groovy and Rest services interact, how to tests such APIs with Spock to be "Enterprisey"!

Published in: Technology

Take a Groovy REST

  1. 1. SPRINGONE2GX WASHINGTON, DC Take a Groovy REST! by Guillaume Laforge @glaforge
  2. 2. Promo Code: ctwspringo2gx 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. SPRINGONE2GX WASHINGTON, DC 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. SPRINGONE2GX WASHINGTON, DC 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. SPRINGONE2GX WASHINGTON, DC REST client-side
  52. 52. SPRINGONE2GX WASHINGTON, DC 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"])
 @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'
  78. 78. SPRINGONE2GX WASHINGTON, DC Miscellaneous
  79. 79. SPRINGONE2GX WASHINGTON, DC Miscellaneous
  80. 80. @glaforge httpie 45
  81. 81. @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'))
  82. 82. @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')
 }
 }
 }
  83. 83. @glaforge Runscope 48
  84. 84. @glaforge Mockbin 49
  85. 85. @glaforge Httpbin 50
  86. 86. @glaforge Requestb.in 51
  87. 87. Arg! The Groovy REST force is too strong!
  88. 88. SPRINGONE2GX WASHINGTON, DC Thanks for your attention!
  89. 89. SPRINGONE2GX WASHINGTON, DC Questions & Answers
  90. 90. @glaforge 55 Help us move Groovy forward! Thanks! Take a Groovy REST! @glaforge

×