Skip to content

Commit 24b6be0

Browse files
authored
DXE-2911 Merge pull request #46 from Chazoshtare/feature/apache-httpclient5-bindings
DXE-2911 Add bindings for Apache HTTP Client5
2 parents 6ac7afb + 73896d1 commit 24b6be0

File tree

10 files changed

+484
-1
lines changed

10 files changed

+484
-1
lines changed

edgegrid-signer-apache-http-client/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
-[![Javadoc](http://www.javadoc.io/badge/com.akamai.edgegrid/edgegrid-signer-apache-http-client.svg)](http://www.javadoc.io/doc/com.akamai.edgegrid/edgegrid-signer-apache-http-client)
55

66
This library implements [Akamai EdgeGrid Authentication](https://techdocs.akamai.com/developer/docs/authenticate-with-edgegrid) for Java.
7-
This particular module is a binding for the [Apache HTTP Client library](https://hc.apache.org/).
7+
This particular module is a binding for the [Apache HTTP Client library](https://hc.apache.org/) versions before 5.0.0.
8+
For Apache HTTP Client >= 5.0.0 support, use `edgegrid-signer-apache-http-client5` module.
89
This project contains installation and usage instructions in the [README.md](../README.md).
910

1011
## Use Apache HTTP Client
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Apache HTTP Client 5 module - EdgeGrid Client for Java
2+
3+
-[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.akamai.edgegrid/edgegrid-signer-apache-http-client5/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.akamai.edgegrid/edgegrid-signer-apache-http-client5)
4+
-[![Javadoc](http://www.javadoc.io/badge/com.akamai.edgegrid/edgegrid-signer-apache-http-client5.svg)](http://www.javadoc.io/doc/com.akamai.edgegrid/edgegrid-signer-apache-http-client5)
5+
6+
This library
7+
implements [Akamai EdgeGrid Authentication](https://techdocs.akamai.com/developer/docs/authenticate-with-edgegrid) for
8+
Java.
9+
This particular module is a binding for the [Apache HTTP Client library version 5.x](https://hc.apache.org/).
10+
This project contains installation and usage instructions in the [README.md](../README.md).
11+
12+
## Use Apache HTTP Client
13+
14+
Include the following Maven dependency in your project POM:
15+
16+
```xml
17+
<dependency>
18+
<groupId>com.akamai.edgegrid</groupId>
19+
<artifactId>edgegrid-signer-apache-http-client5</artifactId>
20+
<version>${version}</version>
21+
</dependency>
22+
```
23+
24+
Or in Gradle's `build.gradle.kts`
25+
```kotlin
26+
implementation("com.akamai.edgegrid:edgegrid-signer-apache-http-client5:$version")
27+
```
28+
29+
Create an HTTP client that will sign your HTTP request with a defined client credential:
30+
31+
```java
32+
var client=HttpClientBuilder.create()
33+
.addRequestInterceptorFirst(new ApacheHttpClient5EdgeGridInterceptor(credential))
34+
.setRoutePlanner(new ApacheHttpClient5EdgeGridRoutePlanner(credential))
35+
.build();
36+
37+
var request=new HttpGet("http://endpoint.net/billing-usage/v1/reportSources");
38+
client.execute(request,response->{
39+
// response handler
40+
});
41+
```
42+
43+
## Use with REST-assured
44+
45+
[REST-assured](https://github.com/rest-assured/rest-assured) doesn't currently support Apache HTTP Client 5. Refer to
46+
this [README](/edgegrid-signer-apache-http-client/README.md) in `edgegrid-signer-apache-http-client` module to set up
47+
an interceptor for a legacy client.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
6+
<parent>
7+
<artifactId>edgegrid-signer-parent</artifactId>
8+
<groupId>com.akamai.edgegrid</groupId>
9+
<version>5.0.0</version>
10+
</parent>
11+
<modelVersion>4.0.0</modelVersion>
12+
13+
<artifactId>edgegrid-signer-apache-http-client5</artifactId>
14+
<packaging>jar</packaging>
15+
<name>Apache HTTP Client 5 Library binding for EdgeGrid Client</name>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>com.akamai.edgegrid</groupId>
20+
<artifactId>edgegrid-signer-core</artifactId>
21+
<version>${project.version}</version>
22+
</dependency>
23+
<dependency>
24+
<groupId>ch.qos.logback</groupId>
25+
<artifactId>logback-classic</artifactId>
26+
</dependency>
27+
<dependency>
28+
<groupId>com.github.tomakehurst</groupId>
29+
<artifactId>wiremock</artifactId>
30+
<scope>test</scope>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.apache.httpcomponents.client5</groupId>
34+
<artifactId>httpclient5</artifactId>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.apache.httpcomponents.core5</groupId>
38+
<artifactId>httpcore5</artifactId>
39+
</dependency>
40+
<dependency>
41+
<groupId>org.hamcrest</groupId>
42+
<artifactId>hamcrest-all</artifactId>
43+
</dependency>
44+
<!-- Apache HTTP Client library uses JCL. -->
45+
<dependency>
46+
<groupId>org.slf4j</groupId>
47+
<artifactId>jcl-over-slf4j</artifactId>
48+
</dependency>
49+
<dependency>
50+
<groupId>org.slf4j</groupId>
51+
<artifactId>slf4j-api</artifactId>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.testng</groupId>
55+
<artifactId>testng</artifactId>
56+
</dependency>
57+
</dependencies>
58+
59+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.akamai.edgegrid.signer.apachehttpclient5;
2+
3+
import com.akamai.edgegrid.signer.ClientCredential;
4+
import com.akamai.edgegrid.signer.ClientCredentialProvider;
5+
import com.akamai.edgegrid.signer.Request;
6+
import com.akamai.edgegrid.signer.exceptions.RequestSigningException;
7+
import org.apache.hc.core5.http.EntityDetails;
8+
import org.apache.hc.core5.http.HttpRequest;
9+
import org.apache.hc.core5.http.HttpRequestInterceptor;
10+
import org.apache.hc.core5.http.protocol.HttpContext;
11+
12+
/**
13+
* Apache HTTP Client5 Library interceptor that signs a request using EdgeGrid V1 signing algorithm.
14+
* Signing is a process of adding an Authorization header with a request signature. If signing fails then <code>RuntimeException</code> is thrown.
15+
*/
16+
public class ApacheHttpClient5EdgeGridInterceptor implements HttpRequestInterceptor {
17+
18+
private final ApacheHttpClient5EdgeGridRequestSigner binding;
19+
20+
/**
21+
* Creates an EdgeGrid signing interceptor using the same {@link ClientCredential} for each
22+
* request.
23+
*
24+
* @param credential a {@link ClientCredential}
25+
*/
26+
public ApacheHttpClient5EdgeGridInterceptor(ClientCredential credential) {
27+
this.binding = new ApacheHttpClient5EdgeGridRequestSigner(credential);
28+
}
29+
30+
/**
31+
* Creates an EdgeGrid signing interceptor selecting a {@link ClientCredential} via
32+
* {@link ClientCredentialProvider#getClientCredential(Request)} for each request.
33+
*
34+
* @param clientCredentialProvider a {@link ClientCredentialProvider}
35+
*/
36+
public ApacheHttpClient5EdgeGridInterceptor(ClientCredentialProvider clientCredentialProvider) {
37+
this.binding = new ApacheHttpClient5EdgeGridRequestSigner(clientCredentialProvider);
38+
}
39+
40+
@Override
41+
public void process(
42+
HttpRequest request,
43+
EntityDetails entityDetails,
44+
HttpContext httpContext
45+
) {
46+
try {
47+
binding.sign(request, request);
48+
} catch (RequestSigningException e) {
49+
throw new RuntimeException(e);
50+
}
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.akamai.edgegrid.signer.apachehttpclient5;
2+
3+
import com.akamai.edgegrid.signer.AbstractEdgeGridRequestSigner;
4+
import com.akamai.edgegrid.signer.ClientCredential;
5+
import com.akamai.edgegrid.signer.ClientCredentialProvider;
6+
import com.akamai.edgegrid.signer.Request;
7+
import org.apache.hc.core5.http.Header;
8+
import org.apache.hc.core5.http.HttpEntityContainer;
9+
import org.apache.hc.core5.http.HttpRequest;
10+
import org.apache.hc.core5.http.io.entity.BufferedHttpEntity;
11+
import org.apache.hc.core5.http.io.entity.EntityUtils;
12+
13+
import java.io.IOException;
14+
import java.net.URI;
15+
import java.net.URISyntaxException;
16+
17+
/**
18+
* Apache HTTP Client5 binding for EdgeGrid signer for signing {@link HttpRequest}.
19+
*/
20+
public class ApacheHttpClient5EdgeGridRequestSigner extends AbstractEdgeGridRequestSigner<HttpRequest, HttpRequest> {
21+
22+
public ApacheHttpClient5EdgeGridRequestSigner(ClientCredential clientCredential) {
23+
super(clientCredential);
24+
}
25+
26+
public ApacheHttpClient5EdgeGridRequestSigner(ClientCredentialProvider clientCredentialProvider) {
27+
super(clientCredentialProvider);
28+
}
29+
30+
@Override
31+
protected URI requestUri(HttpRequest request) {
32+
return getUri(request);
33+
}
34+
35+
@Override
36+
protected Request map(HttpRequest request) {
37+
Request.RequestBuilder builder = Request.builder()
38+
.method(request.getMethod())
39+
.uri(getUri(request))
40+
.body(serializeContent(request));
41+
for (Header h : request.getHeaders()) {
42+
builder.header(h.getName(), h.getValue());
43+
}
44+
45+
return builder.build();
46+
}
47+
48+
private URI getUri(HttpRequest request) {
49+
try {
50+
return request.getUri();
51+
} catch (URISyntaxException e) {
52+
throw new IllegalArgumentException(e.getMessage(), e);
53+
}
54+
}
55+
56+
private byte[] serializeContent(HttpRequest request) {
57+
if (!(request instanceof HttpEntityContainer)) {
58+
return new byte[]{};
59+
}
60+
61+
var entityWithRequest = (HttpEntityContainer) request;
62+
var entity = entityWithRequest.getEntity();
63+
if (entity == null) {
64+
return new byte[]{};
65+
}
66+
67+
try {
68+
// Buffer non-repeatable entities
69+
if (!entity.isRepeatable()) {
70+
entityWithRequest.setEntity(new BufferedHttpEntity(entity));
71+
}
72+
return EntityUtils.toByteArray(entityWithRequest.getEntity());
73+
} catch (IOException e) {
74+
throw new RuntimeException(e);
75+
}
76+
}
77+
78+
@Override
79+
protected void setAuthorization(HttpRequest request, String signature) {
80+
request.setHeader("Authorization", signature);
81+
}
82+
83+
@Override
84+
protected void setHost(HttpRequest request, String host, URI uri) {
85+
request.setHeader("Host", host);
86+
setRequestUri(request, uri);
87+
}
88+
89+
private void setRequestUri(HttpRequest request, URI uri) {
90+
// temporary workaround for https://issues.apache.org/jira/browse/HTTPCORE-742
91+
request.setPath(uri.getPath());
92+
request.setUri(uri);
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.akamai.edgegrid.signer.apachehttpclient5;
2+
3+
import com.akamai.edgegrid.signer.ClientCredential;
4+
import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
5+
import org.apache.hc.core5.http.HttpHost;
6+
import org.apache.hc.core5.http.protocol.HttpContext;
7+
8+
import java.net.ProxySelector;
9+
10+
public class ApacheHttpClient5EdgeGridRoutePlanner extends SystemDefaultRoutePlanner {
11+
12+
private final ClientCredential clientCredential;
13+
14+
public ApacheHttpClient5EdgeGridRoutePlanner(ClientCredential clientCredential) {
15+
super(ProxySelector.getDefault());
16+
this.clientCredential = clientCredential;
17+
}
18+
19+
@Override
20+
protected HttpHost determineProxy(HttpHost target, HttpContext context) {
21+
var hostname = clientCredential.getHost();
22+
int port = -1;
23+
final int pos = hostname.lastIndexOf(":");
24+
if (pos > 0) {
25+
try {
26+
port = Integer.parseInt(hostname.substring(pos + 1));
27+
} catch (NumberFormatException ex) {
28+
throw new IllegalArgumentException("Host contains invalid port number: " + hostname);
29+
}
30+
hostname = hostname.substring(0, pos);
31+
}
32+
return new HttpHost("https", hostname, port);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.akamai.edgegrid.signer.apachehttpclient5;
2+
3+
import com.akamai.edgegrid.signer.ClientCredential;
4+
import com.github.tomakehurst.wiremock.WireMockServer;
5+
import com.github.tomakehurst.wiremock.matching.RequestPattern;
6+
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
7+
import org.apache.hc.client5.http.classic.methods.HttpGet;
8+
import org.hamcrest.CoreMatchers;
9+
import org.hamcrest.MatcherAssert;
10+
import org.hamcrest.Matchers;
11+
import org.testng.annotations.AfterClass;
12+
import org.testng.annotations.BeforeClass;
13+
import org.testng.annotations.BeforeMethod;
14+
import org.testng.annotations.Test;
15+
16+
import java.io.IOException;
17+
import java.util.List;
18+
19+
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
20+
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
21+
import static com.github.tomakehurst.wiremock.client.WireMock.get;
22+
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
23+
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
24+
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
25+
26+
/**
27+
* Integration tests for {@link ApacheHttpClient5EdgeGridInterceptor}.
28+
*/
29+
public class ApacheHttpClient5EdgeGridInterceptorIntegrationTest {
30+
31+
static final String SERVICE_MOCK_HOST = "localhost";
32+
33+
WireMockServer wireMockServer = new WireMockServer(wireMockConfig().dynamicHttpsPort());
34+
35+
ClientCredential credential;
36+
37+
private String getHost() {
38+
return SERVICE_MOCK_HOST + ":" + wireMockServer.httpsPort();
39+
}
40+
41+
@BeforeClass
42+
public void setUp() {
43+
wireMockServer.start();
44+
credential = ClientCredential.builder()
45+
.accessToken("akaa-dm5g2bfwoodqnc6k-ju7vlao2wz6oz2rp")
46+
.clientToken("akaa-k7glklzuxkkh2ycw-oadjphopvpn6yjoj")
47+
.clientSecret("SOMESECRET")
48+
.host(getHost())
49+
.build();
50+
}
51+
52+
@BeforeMethod
53+
public void reset() {
54+
wireMockServer.resetMappings();
55+
wireMockServer.resetRequests();
56+
}
57+
58+
@AfterClass
59+
public void tearDownAll() {
60+
wireMockServer.stop();
61+
}
62+
63+
@Test
64+
public void testInterceptor() throws IOException {
65+
wireMockServer.stubFor(get(urlPathEqualTo("/billing-usage/v1/reportSources"))
66+
.withHeader("Authorization", matching(".*"))
67+
.withHeader("Host", equalTo(getHost()))
68+
.willReturn(aResponse()
69+
.withStatus(302)
70+
.withHeader("Location", "/billing-usage/v1/reportSources/alternative")));
71+
72+
wireMockServer.stubFor(get(urlPathEqualTo("/billing-usage/v1/reportSources/alternative"))
73+
.withHeader("Authorization", matching(".*"))
74+
.withHeader("Host", equalTo(getHost()))
75+
.willReturn(aResponse()
76+
.withStatus(200)
77+
.withHeader("Content-Type", "text/xml")
78+
.withBody("<response>Some content</response>")));
79+
80+
var request = new HttpGet("http://endpoint.net/billing-usage/v1/reportSources");
81+
82+
var client = HttpClientSetup.getHttpClientWithRelaxedSsl()
83+
.addRequestInterceptorFirst(new ApacheHttpClient5EdgeGridInterceptor(credential))
84+
.setRoutePlanner(new ApacheHttpClient5EdgeGridRoutePlanner(credential))
85+
.build();
86+
87+
client.execute(request, response -> null);
88+
89+
List<LoggedRequest> loggedRequests = wireMockServer.findRequestsMatching(RequestPattern
90+
.everything()).getRequests();
91+
92+
MatcherAssert.assertThat(loggedRequests.get(0).getHeader("Authorization"),
93+
Matchers.not(CoreMatchers.equalTo(loggedRequests.get(1).getHeader("Authorization"))));
94+
}
95+
}

0 commit comments

Comments
 (0)