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.

HTTPBuilder NG: Back From The Dead

872 views

Published on

As presented at Greach 2017

Published in: Software
  • Be the first to comment

HTTPBuilder NG: Back From The Dead

  1. 1. BACK FROM THE DEAD: HTTP BUILDER NG NoamTenne
  2. 2. $WHOAMI Hacking around the JVM for the past ~15Years healthy.io
 
 @NoamTenne http://blog.10ne.org
  3. 3. LET’S ROLL http://bit.ly/2kmffDe
  4. 4. A SHOW OF HANDS http://bit.ly/2nK3I4v
  5. 5. HTTPBUILDER http://read.bi/2kmcYaZ Haha! Good one, HTTPBuilder!
  6. 6. HTTP BUILDER NG http://bit.ly/2l1hjzN
  7. 7. HTTP BUILDER NG Source: github.com/http-builder-ng/http-builder-ng Docs: http-builder-ng.github.io/http-builder-ng/
  8. 8. MEETTHE PEOPLE
  9. 9. IMPLEMENTATIONS Core
  10. 10. INSTALLATION
  11. 11. INSTALLATION compile 'io.github.http-builder-ng:http-builder-ng-core:0.14.1'
  12. 12. INSTALLATION compile 'io.github.http-builder-ng:http-builder-ng-core:0.14.1' compile 'io.github.http-builder-ng:http-builder-ng-apache:0.14.1'
  13. 13. INSTALLATION compile 'io.github.http-builder-ng:http-builder-ng-core:0.14.1' compile 'io.github.http-builder-ng:http-builder-ng-apache:0.14.1' compile 'io.github.http-builder-ng:http-builder-ng-okhttp:0.14.1'
  14. 14. INITIALIZATION - CORE import groovyx.net.http.HttpBuilder class Core { private HttpBuilder httpBuilder void init() { httpBuilder = HttpBuilder.configure() } }
  15. 15. INITIALIZATION - CORE import groovyx.net.http.HttpBuilder class Core { private HttpBuilder httpBuilder void init() { httpBuilder = HttpBuilder.configure() } } Consistent namespace
  16. 16. INITIALIZATION - CORE import groovyx.net.http.HttpBuilder class Core { private HttpBuilder httpBuilder void init() { httpBuilder = HttpBuilder.configure() } } Consistent namespace Consistent configuration method
  17. 17. INITIALIZATION - APACHE import groovyx.net.http.ApacheHttpBuilder import groovyx.net.http.HttpBuilder class Apache { private HttpBuilder httpBuilder void init() { httpBuilder = HttpBuilder.configure({ c -> new ApacheHttpBuilder(c) }) } }
  18. 18. INITIALIZATION - APACHE import groovyx.net.http.ApacheHttpBuilder import groovyx.net.http.HttpBuilder class Apache { private HttpBuilder httpBuilder void init() { httpBuilder = HttpBuilder.configure({ c -> new ApacheHttpBuilder(c) }) } } Factory function for configuration
  19. 19. INITIALIZATION - OKHTTP import groovyx.net.http.HttpBuilder import groovyx.net.http.OkHttpBuilder class Ok { private HttpBuilder httpBuilder void init() { httpBuilder = HttpBuilder.configure({ c -> new OkHttpBuilder(c) }) } }
  20. 20. INITIALIZATION - OKHTTP import groovyx.net.http.HttpBuilder import groovyx.net.http.OkHttpBuilder class Ok { private HttpBuilder httpBuilder void init() { httpBuilder = HttpBuilder.configure({ c -> new OkHttpBuilder(c) }) } } Same factory.
 Different Impl
  21. 21. INITIALIZED! NOW WHAT? def result = HttpBuilder.configure({ request.uri = 'http://serenity.ship' }) .get()
  22. 22. INITIALIZED! NOW WHAT? def result = HttpBuilder.configure({ request.uri = 'http://serenity.ship' }) .get() Configure can access request
  23. 23. INITIALIZED! NOW WHAT? def result = HttpBuilder.configure({ request.uri = 'http://serenity.ship' }) .post({ request.uri.path = '/api' response.success({}) })
  24. 24. INITIALIZED! NOW WHAT? def result = HttpBuilder.configure({ request.uri = 'http://serenity.ship' }) .post({ request.uri.path = '/api' response.success({}) }) Method may extend request config
  25. 25. INITIALIZED! NOW WHAT? def result = HttpBuilder.configure({ request.uri = 'http://serenity.ship' }) .post({ request.uri.path = '/api' response.success({}) }) Method may extend request config Method may also hook to response events
  26. 26. GREAT! BUT LET’S SEETHE GOOD STUFF
  27. 27. GREAT! BUT LET’S SEETHE GOOD STUFF
  28. 28. HEADER PARSERS if (headerName == 'Last-Modified') { //Construct proper date pattern and parse } else if (headerName == 'Age') { //Parse long } else if (headerName == 'Content-Disposition') { //Parse and assemble map }
  29. 29. HEADER PARSERS Easily parse commonly used headers such as:
  30. 30. HEADER PARSERS Easily parse commonly used headers such as: Allow -> CsvList
  31. 31. HEADER PARSERS Easily parse commonly used headers such as: Last-Modified -> HttpDate Allow -> CsvList
  32. 32. HEADER PARSERS Easily parse commonly used headers such as: Last-Modified -> HttpDate Allow -> CsvList Cache-Control -> MapPairs
  33. 33. HEADER PARSERS Easily parse commonly used headers such as: Last-Modified -> HttpDate Allow -> CsvList Cache-Control -> MapPairs
  34. 34. HEADER PARSERS ZonedDateTime date = HttpBuilder.configure { request.uri = 'https://google.com' } .head(ZonedDateTime) { response.success { FromServer resp -> resp.headers.find({ h -> h.key == 'Date' }).parse() } }
  35. 35. HEADER PARSERS ZonedDateTime date = HttpBuilder.configure { request.uri = 'https://google.com' } .head(ZonedDateTime) { response.success { FromServer resp -> resp.headers.find({ h -> h.key == 'Date' }).parse() } } Declare return type
  36. 36. HEADER PARSERS ZonedDateTime date = HttpBuilder.configure { request.uri = 'https://google.com' } .head(ZonedDateTime) { response.success { FromServer resp -> resp.headers.find({ h -> h.key == 'Date' }).parse() } } Declare return type Find the relevant header
  37. 37. HEADER PARSERS ZonedDateTime date = HttpBuilder.configure { request.uri = 'https://google.com' } .head(ZonedDateTime) { response.success { FromServer resp -> resp.headers.find({ h -> h.key == 'Date' }).parse() } } Declare return type Find the relevant headerJust call .parse()
  38. 38. CONTENT PARSERS
  39. 39. CONTENT PARSERS Auto parse response content according to type:
  40. 40. CONTENT PARSERS Auto parse response content according to type: HTML
  41. 41. CONTENT PARSERS Auto parse response content according to type: HTML JSON
  42. 42. CONTENT PARSERS Auto parse response content according to type: HTML JSON XML
  43. 43. CONTENT PARSERS Auto parse response content according to type: HTML JSON CSV XML
  44. 44. CONTENT PARSERS Auto parse response content according to type: HTML JSON CSV XML Text
  45. 45. CONTENT PARSERS Register custom parsers per content type!
  46. 46. CONTENT PARSERS Register custom parsers per content type! def inflated = HttpBuilder.configure { request.uri = 'http://serenity.com/bundle.zip' } .get(String) { response.parser('application/zip') { config, fromServer -> def inflaterStream = new GZIPInputStream(fromServer.inputStream) inflaterStream.getText('UTF-8') } }
  47. 47. CONTENT PARSERS Register custom parsers per content type! def inflated = HttpBuilder.configure { request.uri = 'http://serenity.com/bundle.zip' } .get(String) { response.parser('application/zip') { config, fromServer -> def inflaterStream = new GZIPInputStream(fromServer.inputStream) inflaterStream.getText('UTF-8') } } call .parser()
  48. 48. CONTENT PARSERS Register custom parsers per content type! def inflated = HttpBuilder.configure { request.uri = 'http://serenity.com/bundle.zip' } .get(String) { response.parser('application/zip') { config, fromServer -> def inflaterStream = new GZIPInputStream(fromServer.inputStream) inflaterStream.getText('UTF-8') } } call .parser() Specify content type
  49. 49. CONTENT PARSERS Register custom parsers per content type! def inflated = HttpBuilder.configure { request.uri = 'http://serenity.com/bundle.zip' } .get(String) { response.parser('application/zip') { config, fromServer -> def inflaterStream = new GZIPInputStream(fromServer.inputStream) inflaterStream.getText('UTF-8') } } call .parser() Specify content type Provide closure to handle content
  50. 50. REQUEST INTERCEPTORS Perform an operation on every response received
  51. 51. REQUEST INTERCEPTORS - SINGLETYPE HttpBuilder.configure { request.uri = 'http://google.com' execution.interceptor(GET) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } }
  52. 52. REQUEST INTERCEPTORS - SINGLETYPE HttpBuilder.configure { request.uri = 'http://google.com' execution.interceptor(GET) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } } Call interceptor
  53. 53. REQUEST INTERCEPTORS - SINGLETYPE HttpBuilder.configure { request.uri = 'http://google.com' execution.interceptor(GET) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } } Call interceptor Specify method
  54. 54. REQUEST INTERCEPTORS - SINGLETYPE HttpBuilder.configure { request.uri = 'http://google.com' execution.interceptor(GET) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } } Call interceptor Specify method Access config and actual function
  55. 55. REQUEST INTERCEPTORS - SINGLETYPE HttpBuilder.configure { request.uri = 'http://google.com' execution.interceptor(GET) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } } Do the business
  56. 56. REQUEST INTERCEPTORS - SINGLETYPE HttpBuilder.configure { request.uri = 'http://google.com' execution.interceptor(GET) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } } Do the business 3. PROFIT
  57. 57. REQUEST INTERCEPTORS - ANYTYPE HttpBuilder.configure { request.uri = 'http://google.com' HttpVerb[] verbs = [GET, POST, PUT, DELETE] execution.interceptor(verbs) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } }
  58. 58. REQUEST INTERCEPTORS - ANYTYPE HttpBuilder.configure { request.uri = 'http://google.com' HttpVerb[] verbs = [GET, POST, PUT, DELETE] execution.interceptor(verbs) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } } Apply to multiple methods
  59. 59. REQUEST INTERCEPTORS - MODIFY RESPONSE HttpBuilder.configure { request.uri = 'http://google.com' HttpVerb[] verbs = [GET, POST, PUT, DELETE] execution.interceptor(verbs) { cfg, fx -> def result = fx.apply(cfg) "magic:marker:${result}" } }
  60. 60. REQUEST INTERCEPTORS - MODIFY RESPONSE HttpBuilder.configure { request.uri = 'http://google.com' HttpVerb[] verbs = [GET, POST, PUT, DELETE] execution.interceptor(verbs) { cfg, fx -> def result = fx.apply(cfg) "magic:marker:${result}" } } Modify the value!
  61. 61. REQUEST ENCODERS Perform an operation on every request sent
  62. 62. REQUEST ENCODERS { "body": {}, "metadata": { "clientId": "abcdef123456789" } }
  63. 63. REQUEST ENCODERS HttpBuilder.configure { request.uri = 'http://serenity.com/report' request.encoder('application/json') { config, ToServer req -> def w = "{"body":${config.request.body},"clientId": ”bla"" req.toServer(new ByteArrayInputStream(w.bytes)) } }
  64. 64. REQUEST ENCODERS HttpBuilder.configure { request.uri = 'http://serenity.com/report' request.encoder('application/json') { config, ToServer req -> def w = "{"body":${config.request.body},"clientId": ”bla"" req.toServer(new ByteArrayInputStream(w.bytes)) } } call .encoder()
  65. 65. REQUEST ENCODERS HttpBuilder.configure { request.uri = 'http://serenity.com/report' request.encoder('application/json') { config, ToServer req -> def w = "{"body":${config.request.body},"clientId": ”bla"" req.toServer(new ByteArrayInputStream(w.bytes)) } } call .encoder() Specify content type
  66. 66. REQUEST ENCODERS HttpBuilder.configure { request.uri = 'http://serenity.com/report' request.encoder('application/json') { config, ToServer req -> def w = "{"body":${config.request.body},"clientId": ”bla"" req.toServer(new ByteArrayInputStream(w.bytes)) } } Modified content back to the server handle
  67. 67. BONUS: CAN BE USED BY JAVA Use Java 8 lambdas or function objects
  68. 68. BONUS: DIY
  69. 69. BONUS: DIY extends groovyx.net.http.HttpBuilder
  70. 70. BONUS: DIY protected abstract ChainedHttpConfig getObjectConfig()
  71. 71. BONUS: DIY protected abstract ChainedHttpConfig getObjectConfig() Retrieve client configuration
  72. 72. BONUS: DIY protected abstract ChainedHttpConfig getObjectConfig() public abstract Executor getExecutor() Retrieve client configuration
  73. 73. BONUS: DIY protected abstract ChainedHttpConfig getObjectConfig() public abstract Executor getExecutor() Provides a threading interface Retrieve client configuration
  74. 74. BONUS: DIY protected abstract Object doGet(final ChainedHttpConfig config) protected abstract Object doHead(final ChainedHttpConfig config) protected abstract Object doPost(final ChainedHttpConfig config) protected abstract Object doPut(final ChainedHttpConfig config) protected abstract Object doDelete(final ChainedHttpConfig config)
  75. 75. BONUS:TESTABLE
  76. 76. EXAMPLES

×