httpbuilder ng: back from the dead

Post on 22-Jan-2018

743 Views

Category:

Software

9 Downloads

Preview:

Click to see full reader

TRANSCRIPT

BACK FROM THE DEAD:

HTTP BUILDER NG

Noam Tenne

$WHOAMI

Hacking around the JVM for the past ~15 Years

healthy.io

@NoamTenne

http://blog.10ne.org

LET’S ROLL

http://bit.ly/2kmffDe

A SHOW OF HANDS

http://bit.ly/2nK3I4v

HTTPBUILDER

http://read.bi/2kmcYaZ

Haha! Good one, HTTPBuilder!

HTTP BUILDER NG

http://bit.ly/2l1hjzN

HTTP BUILDER NG

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

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

MEET THE PEOPLE

IMPLEMENTATIONS

Core

INSTALLATION

INSTALLATION

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

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'

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'

INITIALIZATION - COREimport groovyx.net.http.HttpBuilder

class Core { private HttpBuilder httpBuilder

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

INITIALIZATION - COREimport groovyx.net.http.HttpBuilder

class Core { private HttpBuilder httpBuilder

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

Consistent namespace

INITIALIZATION - COREimport groovyx.net.http.HttpBuilder

class Core { private HttpBuilder httpBuilder

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

Consistent namespace

Consistent configuration method

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

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

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

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

INITIALIZED! NOW WHAT?

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

INITIALIZED! NOW WHAT?

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

Configure can access request

INITIALIZED! NOW WHAT?

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

INITIALIZED! NOW WHAT?

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

Method may extend request config

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

GREAT!BUT LET’S SEE THE GOOD STUFF

GREAT!BUT LET’S SEE THE GOOD STUFF

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 }

HEADER PARSERS

Easily parse commonly used headers such as:

HEADER PARSERS

Easily parse commonly used headers such as:

Allow -> CsvList

HEADER PARSERS

Easily parse commonly used headers such as:

Last-Modified -> HttpDate

Allow -> CsvList

HEADER PARSERS

Easily parse commonly used headers such as:

Last-Modified -> HttpDate

Allow -> CsvList

Cache-Control -> MapPairs

HEADER PARSERS

Easily parse commonly used headers such as:

Last-Modified -> HttpDate

Allow -> CsvList

Cache-Control -> MapPairs

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

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

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

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

CONTENT PARSERS

CONTENT PARSERS

Auto parse response content according to type:

CONTENT PARSERS

Auto parse response content according to type:

HTML

CONTENT PARSERS

Auto parse response content according to type:

HTML

JSON

CONTENT PARSERS

Auto parse response content according to type:

HTML

JSON XML

CONTENT PARSERS

Auto parse response content according to type:

HTML

JSON

CSV

XML

CONTENT PARSERS

Auto parse response content according to type:

HTML

JSON

CSV

XML

Text

CONTENT PARSERSRegister custom parsers per content type!

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

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

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

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

REQUEST INTERCEPTORS

Perform an operation on every response received

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

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

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

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

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

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

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

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

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

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!

REQUEST ENCODERS

Perform an operation on every request sent

REQUEST ENCODERS

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

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

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

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

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

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

BONUS: DIY

BONUS: DIYextends groovyx.net.http.HttpBuilder

BONUS: DIY

protected abstract ChainedHttpConfig getObjectConfig()

BONUS: DIY

protected abstract ChainedHttpConfig getObjectConfig()

Retrieve client configuration

BONUS: DIY

protected abstract ChainedHttpConfig getObjectConfig()

public abstract Executor getExecutor()

Retrieve client configuration

BONUS: DIY

protected abstract ChainedHttpConfig getObjectConfig()

public abstract Executor getExecutor()

Provides a threading interface

Retrieve client configuration

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)

BONUS: TESTABLE

EXAMPLES

top related