BACK FROM THE DEAD:
HTTP BUILDER NG
NoamTenne
$WHOAMI
Hacking around the JVM for the past ~15Years
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/
MEETTHE 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 - CORE
import groovyx.net.http.HttpBuilder
class Core {
private HttpBuilder httpBuilder
void init() {
httpBuilder = HttpBuilder.configure()
}
}
INITIALIZATION - CORE
import groovyx.net.http.HttpBuilder
class Core {
private HttpBuilder httpBuilder
void init() {
httpBuilder = HttpBuilder.configure()
}
}
Consistent namespace
INITIALIZATION - CORE
import groovyx.net.http.HttpBuilder
class Core {
private HttpBuilder httpBuilder
void init() {
httpBuilder = HttpBuilder.configure()
}
}
Consistent namespace
Consistent
configuration method
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)
})
}
}
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
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)
})
}
}
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
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 SEETHE GOOD STUFF
GREAT!
BUT LET’S SEETHE 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 PARSERS
ZonedDateTime date = HttpBuilder.configure {
request.uri = 'https://google.com'
}
.head(ZonedDateTime) {
response.success { FromServer resp ->
resp.headers.find({ h ->
h.key == 'Date'
}).parse()
}
}
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
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
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()
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 PARSERS
Register custom parsers per content type!
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')
}
}
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()
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
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
REQUEST INTERCEPTORS
Perform an operation on every response received
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)
}
}
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
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
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
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
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
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)
}
}
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
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 JAVA
Use Java 8 lambdas or function objects
BONUS: DIY
BONUS: DIY
extends 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
HTTPBuilder NG: Back From The Dead
HTTPBuilder NG: Back From The Dead

HTTPBuilder NG: Back From The Dead