Skip to content

Commit

Permalink
Add bindings for Apache HTTP Client5
Browse files Browse the repository at this point in the history
Add info about REST-assured to client5 signer readme
Include info about new bindings in older client bindings README.md
  • Loading branch information
Chazoshtare committed Aug 14, 2023
1 parent 1b5a89a commit 73896d1
Show file tree
Hide file tree
Showing 10 changed files with 484 additions and 1 deletion.
3 changes: 2 additions & 1 deletion edgegrid-signer-apache-http-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
-[![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)

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

## Use Apache HTTP Client
Expand Down
47 changes: 47 additions & 0 deletions edgegrid-signer-apache-http-client5/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Apache HTTP Client 5 module - EdgeGrid Client for Java

-[![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)
-[![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)

This library
implements [Akamai EdgeGrid Authentication](https://techdocs.akamai.com/developer/docs/authenticate-with-edgegrid) for
Java.
This particular module is a binding for the [Apache HTTP Client library version 5.x](https://hc.apache.org/).
This project contains installation and usage instructions in the [README.md](../README.md).

## Use Apache HTTP Client

Include the following Maven dependency in your project POM:

```xml
<dependency>
<groupId>com.akamai.edgegrid</groupId>
<artifactId>edgegrid-signer-apache-http-client5</artifactId>
<version>${version}</version>
</dependency>
```

Or in Gradle's `build.gradle.kts`
```kotlin
implementation("com.akamai.edgegrid:edgegrid-signer-apache-http-client5:$version")
```

Create an HTTP client that will sign your HTTP request with a defined client credential:

```java
var client=HttpClientBuilder.create()
.addRequestInterceptorFirst(new ApacheHttpClient5EdgeGridInterceptor(credential))
.setRoutePlanner(new ApacheHttpClient5EdgeGridRoutePlanner(credential))
.build();

var request=new HttpGet("http://endpoint.net/billing-usage/v1/reportSources");
client.execute(request,response->{
// response handler
});
```

## Use with REST-assured

[REST-assured](https://github.com/rest-assured/rest-assured) doesn't currently support Apache HTTP Client 5. Refer to
this [README](/edgegrid-signer-apache-http-client/README.md) in `edgegrid-signer-apache-http-client` module to set up
an interceptor for a legacy client.
59 changes: 59 additions & 0 deletions edgegrid-signer-apache-http-client5/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<parent>
<artifactId>edgegrid-signer-parent</artifactId>
<groupId>com.akamai.edgegrid</groupId>
<version>5.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>edgegrid-signer-apache-http-client5</artifactId>
<packaging>jar</packaging>
<name>Apache HTTP Client 5 Library binding for EdgeGrid Client</name>

<dependencies>
<dependency>
<groupId>com.akamai.edgegrid</groupId>
<artifactId>edgegrid-signer-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5</artifactId>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
</dependency>
<!-- Apache HTTP Client library uses JCL. -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.akamai.edgegrid.signer.apachehttpclient5;

import com.akamai.edgegrid.signer.ClientCredential;
import com.akamai.edgegrid.signer.ClientCredentialProvider;
import com.akamai.edgegrid.signer.Request;
import com.akamai.edgegrid.signer.exceptions.RequestSigningException;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.protocol.HttpContext;

/**
* Apache HTTP Client5 Library interceptor that signs a request using EdgeGrid V1 signing algorithm.
* Signing is a process of adding an Authorization header with a request signature. If signing fails then <code>RuntimeException</code> is thrown.
*/
public class ApacheHttpClient5EdgeGridInterceptor implements HttpRequestInterceptor {

private final ApacheHttpClient5EdgeGridRequestSigner binding;

/**
* Creates an EdgeGrid signing interceptor using the same {@link ClientCredential} for each
* request.
*
* @param credential a {@link ClientCredential}
*/
public ApacheHttpClient5EdgeGridInterceptor(ClientCredential credential) {
this.binding = new ApacheHttpClient5EdgeGridRequestSigner(credential);
}

/**
* Creates an EdgeGrid signing interceptor selecting a {@link ClientCredential} via
* {@link ClientCredentialProvider#getClientCredential(Request)} for each request.
*
* @param clientCredentialProvider a {@link ClientCredentialProvider}
*/
public ApacheHttpClient5EdgeGridInterceptor(ClientCredentialProvider clientCredentialProvider) {
this.binding = new ApacheHttpClient5EdgeGridRequestSigner(clientCredentialProvider);
}

@Override
public void process(
HttpRequest request,
EntityDetails entityDetails,
HttpContext httpContext
) {
try {
binding.sign(request, request);
} catch (RequestSigningException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.akamai.edgegrid.signer.apachehttpclient5;

import com.akamai.edgegrid.signer.AbstractEdgeGridRequestSigner;
import com.akamai.edgegrid.signer.ClientCredential;
import com.akamai.edgegrid.signer.ClientCredentialProvider;
import com.akamai.edgegrid.signer.Request;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntityContainer;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.io.entity.BufferedHttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

/**
* Apache HTTP Client5 binding for EdgeGrid signer for signing {@link HttpRequest}.
*/
public class ApacheHttpClient5EdgeGridRequestSigner extends AbstractEdgeGridRequestSigner<HttpRequest, HttpRequest> {

public ApacheHttpClient5EdgeGridRequestSigner(ClientCredential clientCredential) {
super(clientCredential);
}

public ApacheHttpClient5EdgeGridRequestSigner(ClientCredentialProvider clientCredentialProvider) {
super(clientCredentialProvider);
}

@Override
protected URI requestUri(HttpRequest request) {
return getUri(request);
}

@Override
protected Request map(HttpRequest request) {
Request.RequestBuilder builder = Request.builder()
.method(request.getMethod())
.uri(getUri(request))
.body(serializeContent(request));
for (Header h : request.getHeaders()) {
builder.header(h.getName(), h.getValue());
}

return builder.build();
}

private URI getUri(HttpRequest request) {
try {
return request.getUri();
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}

private byte[] serializeContent(HttpRequest request) {
if (!(request instanceof HttpEntityContainer)) {
return new byte[]{};
}

var entityWithRequest = (HttpEntityContainer) request;
var entity = entityWithRequest.getEntity();
if (entity == null) {
return new byte[]{};
}

try {
// Buffer non-repeatable entities
if (!entity.isRepeatable()) {
entityWithRequest.setEntity(new BufferedHttpEntity(entity));
}
return EntityUtils.toByteArray(entityWithRequest.getEntity());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
protected void setAuthorization(HttpRequest request, String signature) {
request.setHeader("Authorization", signature);
}

@Override
protected void setHost(HttpRequest request, String host, URI uri) {
request.setHeader("Host", host);
setRequestUri(request, uri);
}

private void setRequestUri(HttpRequest request, URI uri) {
// temporary workaround for https://issues.apache.org/jira/browse/HTTPCORE-742
request.setPath(uri.getPath());
request.setUri(uri);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.akamai.edgegrid.signer.apachehttpclient5;

import com.akamai.edgegrid.signer.ClientCredential;
import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.protocol.HttpContext;

import java.net.ProxySelector;

public class ApacheHttpClient5EdgeGridRoutePlanner extends SystemDefaultRoutePlanner {

private final ClientCredential clientCredential;

public ApacheHttpClient5EdgeGridRoutePlanner(ClientCredential clientCredential) {
super(ProxySelector.getDefault());
this.clientCredential = clientCredential;
}

@Override
protected HttpHost determineProxy(HttpHost target, HttpContext context) {
var hostname = clientCredential.getHost();
int port = -1;
final int pos = hostname.lastIndexOf(":");
if (pos > 0) {
try {
port = Integer.parseInt(hostname.substring(pos + 1));
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Host contains invalid port number: " + hostname);
}
hostname = hostname.substring(0, pos);
}
return new HttpHost("https", hostname, port);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.akamai.edgegrid.signer.apachehttpclient5;

import com.akamai.edgegrid.signer.ClientCredential;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.matching.RequestPattern;
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.io.IOException;
import java.util.List;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;

/**
* Integration tests for {@link ApacheHttpClient5EdgeGridInterceptor}.
*/
public class ApacheHttpClient5EdgeGridInterceptorIntegrationTest {

static final String SERVICE_MOCK_HOST = "localhost";

WireMockServer wireMockServer = new WireMockServer(wireMockConfig().dynamicHttpsPort());

ClientCredential credential;

private String getHost() {
return SERVICE_MOCK_HOST + ":" + wireMockServer.httpsPort();
}

@BeforeClass
public void setUp() {
wireMockServer.start();
credential = ClientCredential.builder()
.accessToken("akaa-dm5g2bfwoodqnc6k-ju7vlao2wz6oz2rp")
.clientToken("akaa-k7glklzuxkkh2ycw-oadjphopvpn6yjoj")
.clientSecret("SOMESECRET")
.host(getHost())
.build();
}

@BeforeMethod
public void reset() {
wireMockServer.resetMappings();
wireMockServer.resetRequests();
}

@AfterClass
public void tearDownAll() {
wireMockServer.stop();
}

@Test
public void testInterceptor() throws IOException {
wireMockServer.stubFor(get(urlPathEqualTo("/billing-usage/v1/reportSources"))
.withHeader("Authorization", matching(".*"))
.withHeader("Host", equalTo(getHost()))
.willReturn(aResponse()
.withStatus(302)
.withHeader("Location", "/billing-usage/v1/reportSources/alternative")));

wireMockServer.stubFor(get(urlPathEqualTo("/billing-usage/v1/reportSources/alternative"))
.withHeader("Authorization", matching(".*"))
.withHeader("Host", equalTo(getHost()))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "text/xml")
.withBody("<response>Some content</response>")));

var request = new HttpGet("http://endpoint.net/billing-usage/v1/reportSources");

var client = HttpClientSetup.getHttpClientWithRelaxedSsl()
.addRequestInterceptorFirst(new ApacheHttpClient5EdgeGridInterceptor(credential))
.setRoutePlanner(new ApacheHttpClient5EdgeGridRoutePlanner(credential))
.build();

client.execute(request, response -> null);

List<LoggedRequest> loggedRequests = wireMockServer.findRequestsMatching(RequestPattern
.everything()).getRequests();

MatcherAssert.assertThat(loggedRequests.get(0).getHeader("Authorization"),
Matchers.not(CoreMatchers.equalTo(loggedRequests.get(1).getHeader("Authorization"))));
}
}
Loading

0 comments on commit 73896d1

Please sign in to comment.