httpbuilder ng: back from the dead

78
BACK FROM THE DEAD: HTTP BUILDER NG Noam Tenne

Upload: noamt

Post on 22-Jan-2018

743 views

Category:

Software


9 download

TRANSCRIPT

Page 1: HTTPBuilder NG: Back From The Dead

BACK FROM THE DEAD:

HTTP BUILDER NG

Noam Tenne

Page 2: HTTPBuilder NG: Back From The Dead

$WHOAMI

Hacking around the JVM for the past ~15 Years

healthy.io

@NoamTenne

http://blog.10ne.org

Page 3: HTTPBuilder NG: Back From The Dead

LET’S ROLL

http://bit.ly/2kmffDe

Page 4: HTTPBuilder NG: Back From The Dead

A SHOW OF HANDS

http://bit.ly/2nK3I4v

Page 5: HTTPBuilder NG: Back From The Dead

HTTPBUILDER

http://read.bi/2kmcYaZ

Haha! Good one, HTTPBuilder!

Page 6: HTTPBuilder NG: Back From The Dead

HTTP BUILDER NG

http://bit.ly/2l1hjzN

Page 7: HTTPBuilder NG: Back From The Dead

HTTP BUILDER NG

Source:github.com/http-builder-ng/http-builder-ng

Docs:http-builder-ng.github.io/http-builder-ng/

Page 8: HTTPBuilder NG: Back From The Dead

MEET THE PEOPLE

Page 9: HTTPBuilder NG: Back From The Dead

IMPLEMENTATIONS

Core

Page 10: HTTPBuilder NG: Back From The Dead

INSTALLATION

Page 11: HTTPBuilder NG: Back From The Dead

INSTALLATION

compile 'io.github.http-builder-ng:http-builder-ng-core:0.14.1'

Page 12: HTTPBuilder NG: Back From The Dead

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'

Page 13: HTTPBuilder NG: Back From The Dead

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'

Page 14: HTTPBuilder NG: Back From The Dead

INITIALIZATION - COREimport groovyx.net.http.HttpBuilder

class Core { private HttpBuilder httpBuilder

void init() { httpBuilder = HttpBuilder.configure() } }

Page 15: HTTPBuilder NG: Back From The Dead

INITIALIZATION - COREimport groovyx.net.http.HttpBuilder

class Core { private HttpBuilder httpBuilder

void init() { httpBuilder = HttpBuilder.configure() } }

Consistent namespace

Page 16: HTTPBuilder NG: Back From The Dead

INITIALIZATION - COREimport groovyx.net.http.HttpBuilder

class Core { private HttpBuilder httpBuilder

void init() { httpBuilder = HttpBuilder.configure() } }

Consistent namespace

Consistent configuration method

Page 17: HTTPBuilder NG: Back From The Dead

INITIALIZATION - APACHEimport groovyx.net.http.ApacheHttpBuilder import groovyx.net.http.HttpBuilder

class Apache { private HttpBuilder httpBuilder

void init() { httpBuilder = HttpBuilder.configure({ c -> new ApacheHttpBuilder(c) }) } }

Page 18: HTTPBuilder NG: Back From The Dead

INITIALIZATION - APACHEimport 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

Page 19: HTTPBuilder NG: Back From The Dead

INITIALIZATION - OKHTTPimport groovyx.net.http.HttpBuilder import groovyx.net.http.OkHttpBuilder

class Ok { private HttpBuilder httpBuilder

void init() { httpBuilder = HttpBuilder.configure({ c -> new OkHttpBuilder(c) }) } }

Page 20: HTTPBuilder NG: Back From The Dead

INITIALIZATION - OKHTTPimport 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

Page 21: HTTPBuilder NG: Back From The Dead

INITIALIZED! NOW WHAT?

def result = HttpBuilder.configure({ request.uri = 'http://serenity.ship' }) .get()

Page 22: HTTPBuilder NG: Back From The Dead

INITIALIZED! NOW WHAT?

def result = HttpBuilder.configure({ request.uri = 'http://serenity.ship' }) .get()

Configure can access request

Page 23: HTTPBuilder NG: Back From The Dead

INITIALIZED! NOW WHAT?

def result = HttpBuilder.configure({ request.uri = 'http://serenity.ship' }) .post({ request.uri.path = '/api' response.success({}) })

Page 24: HTTPBuilder NG: Back From The Dead

INITIALIZED! NOW WHAT?

def result = HttpBuilder.configure({ request.uri = 'http://serenity.ship' }) .post({ request.uri.path = '/api' response.success({}) })

Method may extend request config

Page 25: HTTPBuilder NG: Back From The Dead

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

Page 26: HTTPBuilder NG: Back From The Dead

GREAT!BUT LET’S SEE THE GOOD STUFF

Page 27: HTTPBuilder NG: Back From The Dead

GREAT!BUT LET’S SEE THE GOOD STUFF

Page 28: HTTPBuilder NG: Back From The Dead

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 }

Page 29: HTTPBuilder NG: Back From The Dead

HEADER PARSERS

Easily parse commonly used headers such as:

Page 30: HTTPBuilder NG: Back From The Dead

HEADER PARSERS

Easily parse commonly used headers such as:

Allow -> CsvList

Page 31: HTTPBuilder NG: Back From The Dead

HEADER PARSERS

Easily parse commonly used headers such as:

Last-Modified -> HttpDate

Allow -> CsvList

Page 32: HTTPBuilder NG: Back From The Dead

HEADER PARSERS

Easily parse commonly used headers such as:

Last-Modified -> HttpDate

Allow -> CsvList

Cache-Control -> MapPairs

Page 33: HTTPBuilder NG: Back From The Dead

HEADER PARSERS

Easily parse commonly used headers such as:

Last-Modified -> HttpDate

Allow -> CsvList

Cache-Control -> MapPairs

Page 34: HTTPBuilder NG: Back From The Dead

HEADER PARSERSZonedDateTime date = HttpBuilder.configure { request.uri = 'https://google.com' } .head(ZonedDateTime) { response.success { FromServer resp -> resp.headers.find({ h -> h.key == 'Date' }).parse() } }

Page 35: HTTPBuilder NG: Back From The Dead

HEADER PARSERSZonedDateTime 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

Page 36: HTTPBuilder NG: Back From The Dead

HEADER PARSERSZonedDateTime 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

Page 37: HTTPBuilder NG: Back From The Dead

HEADER PARSERSZonedDateTime 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()

Page 38: HTTPBuilder NG: Back From The Dead

CONTENT PARSERS

Page 39: HTTPBuilder NG: Back From The Dead

CONTENT PARSERS

Auto parse response content according to type:

Page 40: HTTPBuilder NG: Back From The Dead

CONTENT PARSERS

Auto parse response content according to type:

HTML

Page 41: HTTPBuilder NG: Back From The Dead

CONTENT PARSERS

Auto parse response content according to type:

HTML

JSON

Page 42: HTTPBuilder NG: Back From The Dead

CONTENT PARSERS

Auto parse response content according to type:

HTML

JSON XML

Page 43: HTTPBuilder NG: Back From The Dead

CONTENT PARSERS

Auto parse response content according to type:

HTML

JSON

CSV

XML

Page 44: HTTPBuilder NG: Back From The Dead

CONTENT PARSERS

Auto parse response content according to type:

HTML

JSON

CSV

XML

Text

Page 45: HTTPBuilder NG: Back From The Dead

CONTENT PARSERSRegister custom parsers per content type!

Page 46: HTTPBuilder NG: Back From The Dead

CONTENT PARSERSRegister 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') } }

Page 47: HTTPBuilder NG: Back From The Dead

CONTENT PARSERSRegister 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()

Page 48: HTTPBuilder NG: Back From The Dead

CONTENT PARSERSRegister 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

Page 49: HTTPBuilder NG: Back From The Dead

CONTENT PARSERSRegister 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

Page 50: HTTPBuilder NG: Back From The Dead

REQUEST INTERCEPTORS

Perform an operation on every response received

Page 51: HTTPBuilder NG: Back From The Dead

REQUEST INTERCEPTORS - SINGLE TYPE

HttpBuilder.configure { request.uri = 'http://google.com' execution.interceptor(GET) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } }

Page 52: HTTPBuilder NG: Back From The Dead

REQUEST INTERCEPTORS - SINGLE TYPE

HttpBuilder.configure { request.uri = 'http://google.com' execution.interceptor(GET) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } }

Call interceptor

Page 53: HTTPBuilder NG: Back From The Dead

REQUEST INTERCEPTORS - SINGLE TYPE

HttpBuilder.configure { request.uri = 'http://google.com' execution.interceptor(GET) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } }

Call interceptorSpecify method

Page 54: HTTPBuilder NG: Back From The Dead

REQUEST INTERCEPTORS - SINGLE TYPE

HttpBuilder.configure { request.uri = 'http://google.com' execution.interceptor(GET) { cfg, fx -> println "${cfg.request.uri.toURI()} was requested" fx.apply(cfg) } }

Call interceptorSpecify method Access config and

actual function

Page 55: HTTPBuilder NG: Back From The Dead

REQUEST INTERCEPTORS - SINGLE TYPE

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

Page 56: HTTPBuilder NG: Back From The Dead

REQUEST INTERCEPTORS - SINGLE TYPE

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

Page 57: HTTPBuilder NG: Back From The Dead

REQUEST INTERCEPTORS - ANY TYPE

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

Page 58: HTTPBuilder NG: Back From The Dead

REQUEST INTERCEPTORS - ANY TYPE

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

Page 59: HTTPBuilder NG: Back From The Dead

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

Page 60: HTTPBuilder NG: Back From The Dead

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!

Page 61: HTTPBuilder NG: Back From The Dead

REQUEST ENCODERS

Perform an operation on every request sent

Page 62: HTTPBuilder NG: Back From The Dead

REQUEST ENCODERS

{ "body": {}, "metadata": { "clientId": "abcdef123456789" } }

Page 63: HTTPBuilder NG: Back From The Dead

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

Page 64: HTTPBuilder NG: Back From The Dead

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()

Page 65: HTTPBuilder NG: Back From The Dead

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

Page 66: HTTPBuilder NG: Back From The Dead

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

Page 67: HTTPBuilder NG: Back From The Dead

BONUS: CAN BE USED BY JAVAUse Java 8 lambdas or function objects

Page 68: HTTPBuilder NG: Back From The Dead

BONUS: DIY

Page 69: HTTPBuilder NG: Back From The Dead

BONUS: DIYextends groovyx.net.http.HttpBuilder

Page 70: HTTPBuilder NG: Back From The Dead

BONUS: DIY

protected abstract ChainedHttpConfig getObjectConfig()

Page 71: HTTPBuilder NG: Back From The Dead

BONUS: DIY

protected abstract ChainedHttpConfig getObjectConfig()

Retrieve client configuration

Page 72: HTTPBuilder NG: Back From The Dead

BONUS: DIY

protected abstract ChainedHttpConfig getObjectConfig()

public abstract Executor getExecutor()

Retrieve client configuration

Page 73: HTTPBuilder NG: Back From The Dead

BONUS: DIY

protected abstract ChainedHttpConfig getObjectConfig()

public abstract Executor getExecutor()

Provides a threading interface

Retrieve client configuration

Page 74: HTTPBuilder NG: Back From The Dead

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)

Page 75: HTTPBuilder NG: Back From The Dead

BONUS: TESTABLE

Page 76: HTTPBuilder NG: Back From The Dead

EXAMPLES

Page 77: HTTPBuilder NG: Back From The Dead
Page 78: HTTPBuilder NG: Back From The Dead