Upload
noamt
View
743
Download
9
Embed Size (px)
Citation preview
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