-
Notifications
You must be signed in to change notification settings - Fork 229
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add interceptors for httpclient5 (via #935)
- Loading branch information
1 parent
10b1bcc
commit 466aa0b
Showing
12 changed files
with
888 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
description = "Allure Apache HttpClient5 Integration" | ||
|
||
dependencies { | ||
api(project(":allure-attachments")) | ||
implementation("org.apache.httpcomponents.client5:httpclient5") | ||
testImplementation("com.github.tomakehurst:wiremock") | ||
testImplementation("io.github.glytching:junit-extensions") | ||
testImplementation("org.assertj:assertj-core") | ||
testImplementation("org.junit.jupiter:junit-jupiter-api") | ||
testImplementation("org.mockito:mockito-core") | ||
testImplementation("org.slf4j:slf4j-simple") | ||
testImplementation(project(":allure-java-commons-test")) | ||
testImplementation(project(":allure-junit-platform")) | ||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") | ||
} | ||
|
||
tasks.jar { | ||
manifest { | ||
attributes(mapOf( | ||
"Automatic-Module-Name" to "io.qameta.allure.httpclient5" | ||
)) | ||
} | ||
} | ||
|
||
tasks.test { | ||
useJUnitPlatform() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
## Allure-httpclient5 | ||
Extended logging for requests and responses with [httpclient5](https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5) | ||
This library does not support `httpclient` due to package and API changes between `httpclient` and `httpclient5`. | ||
To work with `httpclient`, it is recommended to use the `allure-httpclient` library. | ||
|
||
## Wiki | ||
https://hc.apache.org/httpcomponents-client-5.2.x/ | ||
https://hc.apache.org/httpcomponents-client-5.2.x/quickstart.html | ||
https://hc.apache.org/httpcomponents-client-5.2.x/migration-guide/index.html | ||
https://hc.apache.org/httpcomponents-client-5.2.x/examples.html | ||
|
||
## Additional features | ||
Implemented: | ||
- The `httpclient5` library uses `gzip` compression by default. Interceptors attach message bodies in decompressed form | ||
- `HttpEntityEnclosingRequest` is removed from `httpclient5`. Request interceptor works wo `HttpEntityEnclosingRequest` | ||
|
||
Not tested: | ||
- The httpclient5 library support Async interactions (Not tested) | ||
|
||
## Examples | ||
|
||
```java | ||
import org.apache.hc.client5.http.classic.methods.HttpGet; | ||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; | ||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; | ||
import org.apache.hc.core5.http.io.entity.EntityUtils; | ||
import io.qameta.allure.httpclient5.AllureHttpClient5Request; | ||
import io.qameta.allure.httpclient5.AllureHttpClient5Response; | ||
|
||
class Test { | ||
|
||
@Test | ||
void smokeGetShouldNotThrowThenReturnCorrectResponseMessage() throws IOException { | ||
final HttpClientBuilder builder = HttpClientBuilder.create() | ||
.addRequestInterceptorFirst(new AllureHttpClient5Request()) | ||
.addResponseInterceptorLast(new AllureHttpClient5Response()); | ||
|
||
try (CloseableHttpClient httpClient = builder.build()) { | ||
final HttpGet httpGet = new HttpGet("/hello"); | ||
httpClient.execute(httpGet, response -> { | ||
assertThat(EntityUtils.toString(response.getEntity())).isEqualTo(BODY_STRING); | ||
return response; | ||
}); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
In addition to using standard templates for formatting, you can use your custom `ftl` templates along the path | ||
`/resources/tpl/...`. For examples, you can use templates from the `allure-attachments` module. | ||
|
||
```java | ||
final HttpClientBuilder builder = HttpClientBuilder.create() | ||
.addRequestInterceptorFirst(new AllureHttpClient5Request("your-request-template-attachment.ftl")) | ||
.addResponseInterceptorLast(new AllureHttpClient5Response("your-response-template-attachment.ftl")); | ||
``` |
83 changes: 83 additions & 0 deletions
83
allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Request.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* Copyright 2019 Qameta Software OÜ | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.qameta.allure.httpclient5; | ||
|
||
import io.qameta.allure.attachment.AttachmentData; | ||
import io.qameta.allure.attachment.AttachmentProcessor; | ||
import io.qameta.allure.attachment.AttachmentRenderer; | ||
import io.qameta.allure.attachment.DefaultAttachmentProcessor; | ||
import io.qameta.allure.attachment.FreemarkerAttachmentRenderer; | ||
import io.qameta.allure.attachment.http.HttpRequestAttachment; | ||
import org.apache.hc.core5.http.EntityDetails; | ||
import org.apache.hc.core5.http.HttpEntity; | ||
import org.apache.hc.core5.http.HttpRequest; | ||
import org.apache.hc.core5.http.HttpRequestInterceptor; | ||
import org.apache.hc.core5.http.protocol.HttpContext; | ||
|
||
import java.util.stream.Stream; | ||
|
||
import static io.qameta.allure.attachment.http.HttpRequestAttachment.Builder.create; | ||
|
||
/** | ||
* @author a-simeshin (Simeshin Artem) | ||
*/ | ||
@SuppressWarnings("PMD.MethodArgumentCouldBeFinal") | ||
public class AllureHttpClient5Request implements HttpRequestInterceptor { | ||
|
||
private final AttachmentRenderer<AttachmentData> renderer; | ||
private final AttachmentProcessor<AttachmentData> processor; | ||
|
||
public AllureHttpClient5Request() { | ||
this("http-request.ftl"); | ||
} | ||
|
||
public AllureHttpClient5Request(final String templateName) { | ||
this(new FreemarkerAttachmentRenderer(templateName), new DefaultAttachmentProcessor()); | ||
} | ||
|
||
public AllureHttpClient5Request(final AttachmentRenderer<AttachmentData> renderer, | ||
final AttachmentProcessor<AttachmentData> processor) { | ||
this.renderer = renderer; | ||
this.processor = processor; | ||
} | ||
|
||
/** | ||
* Processes the HTTP request and adds an attachment to the Allure Attachment processor. | ||
* | ||
* @param request the HTTP request | ||
* @param entity the entity details | ||
* @param context the HTTP context | ||
*/ | ||
@Override | ||
public void process(HttpRequest request, EntityDetails entity, HttpContext context) { | ||
final String attachmentName = getAttachmentName(request); | ||
final HttpRequestAttachment.Builder builder = create(attachmentName, request.getRequestUri()); | ||
builder.setMethod(request.getMethod()); | ||
|
||
Stream.of(request.getHeaders()).forEach(header -> builder.setHeader(header.getName(), header.getValue())); | ||
|
||
if (entity instanceof HttpEntity && ((HttpEntity) entity).isRepeatable() && entity.getContentLength() != 0) { | ||
builder.setBody(AllureHttpEntityUtils.getBody((HttpEntity) entity)); | ||
} | ||
|
||
processor.addAttachment(builder.build(), renderer); | ||
} | ||
|
||
private String getAttachmentName(final HttpRequest request) { | ||
return String.format("Request_%s_%s", request.getMethod(), request.getRequestUri()); | ||
} | ||
|
||
} |
100 changes: 100 additions & 0 deletions
100
allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Response.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
* Copyright 2019 Qameta Software OÜ | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.qameta.allure.httpclient5; | ||
|
||
import io.qameta.allure.attachment.AttachmentData; | ||
import io.qameta.allure.attachment.AttachmentProcessor; | ||
import io.qameta.allure.attachment.AttachmentRenderer; | ||
import io.qameta.allure.attachment.DefaultAttachmentProcessor; | ||
import io.qameta.allure.attachment.FreemarkerAttachmentRenderer; | ||
import io.qameta.allure.attachment.http.HttpResponseAttachment; | ||
import org.apache.hc.core5.http.EntityDetails; | ||
import org.apache.hc.core5.http.HttpEntity; | ||
import org.apache.hc.core5.http.HttpResponse; | ||
import org.apache.hc.core5.http.HttpResponseInterceptor; | ||
import org.apache.hc.core5.http.io.entity.BufferedHttpEntity; | ||
import org.apache.hc.core5.http.message.BasicClassicHttpResponse; | ||
import org.apache.hc.core5.http.protocol.HttpContext; | ||
|
||
import java.io.IOException; | ||
import java.util.stream.Stream; | ||
|
||
import static io.qameta.allure.attachment.http.HttpResponseAttachment.Builder.create; | ||
|
||
/** | ||
* @author a-simeshin (Simeshin Artem) | ||
*/ | ||
@SuppressWarnings({ | ||
"checkstyle:ParameterAssignment", | ||
"PMD.MethodArgumentCouldBeFinal", | ||
"PMD.AvoidReassigningParameters"}) | ||
public class AllureHttpClient5Response implements HttpResponseInterceptor { | ||
private final AttachmentRenderer<AttachmentData> renderer; | ||
private final AttachmentProcessor<AttachmentData> processor; | ||
private static final String NO_BODY = "No body present"; | ||
|
||
public AllureHttpClient5Response() { | ||
this("http-response.ftl"); | ||
} | ||
|
||
public AllureHttpClient5Response(final String templateName) { | ||
this(new FreemarkerAttachmentRenderer(templateName), new DefaultAttachmentProcessor()); | ||
} | ||
|
||
public AllureHttpClient5Response(final AttachmentRenderer<AttachmentData> renderer, | ||
final AttachmentProcessor<AttachmentData> processor) { | ||
this.renderer = renderer; | ||
this.processor = processor; | ||
} | ||
|
||
/** | ||
* Processes the HTTP response and adds an attachment to the Allure Attachment processor. | ||
* | ||
* @param response the HTTP response | ||
* @param entity the entity details, may be null for no response body responses | ||
* @param context the HTTP context | ||
* @throws IOException if an I/O error occurs | ||
*/ | ||
@Override | ||
public void process(HttpResponse response, EntityDetails entity, HttpContext context) throws IOException { | ||
final HttpResponseAttachment.Builder builder = create("Response"); | ||
builder.setResponseCode(response.getCode()); | ||
|
||
Stream.of(response.getHeaders()).forEach(header -> builder.setHeader(header.getName(), header.getValue())); | ||
|
||
final HttpEntity originalHttpEntity = (HttpEntity) entity; | ||
if (originalHttpEntity != null && !originalHttpEntity.isRepeatable()) { | ||
// Looks like a bug or completely new logic. It's not enough to replace chaining EntityDetails entity. | ||
// To read the response body twice, It needs to put in the context also | ||
entity = new BufferedHttpEntity(originalHttpEntity); | ||
final BasicClassicHttpResponse responseEntity = | ||
(BasicClassicHttpResponse) context.getAttribute("http.response"); | ||
responseEntity.setEntity((HttpEntity) entity); | ||
|
||
final String responseBody = AllureHttpEntityUtils.getBody((HttpEntity) entity); | ||
if (responseBody == null || responseBody.isEmpty()) { | ||
builder.setBody(NO_BODY); | ||
} else { | ||
builder.setBody(responseBody); | ||
} | ||
} else { | ||
builder.setBody(NO_BODY); | ||
} | ||
|
||
processor.addAttachment(builder.build(), renderer); | ||
} | ||
|
||
} |
96 changes: 96 additions & 0 deletions
96
allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpEntityUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/* | ||
* Copyright 2023 Qameta Software OÜ | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.qameta.allure.httpclient5; | ||
|
||
import io.qameta.allure.AllureResultsWriteException; | ||
import org.apache.hc.core5.http.HttpEntity; | ||
import org.apache.hc.core5.http.ParseException; | ||
import org.apache.hc.core5.http.io.entity.EntityUtils; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.io.InputStreamReader; | ||
import java.nio.charset.Charset; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.zip.GZIPInputStream; | ||
|
||
/** | ||
* Utility class for working with HTTP entity in Allure framework. | ||
*/ | ||
@SuppressWarnings({"checkstyle:ParameterAssignment", "PMD.AssignmentInOperand"}) | ||
public final class AllureHttpEntityUtils { | ||
|
||
private AllureHttpEntityUtils() { | ||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); | ||
} | ||
|
||
/** | ||
* Retrieves the body of the HTTP entity as a string. | ||
* | ||
* @param httpEntity the HTTP entity | ||
* @return the body of the HTTP entity as a string | ||
* @throws AllureResultsWriteException if an error occurs while reading the entity body | ||
*/ | ||
static String getBody(final HttpEntity httpEntity) { | ||
try { | ||
final String contentEncoding = httpEntity.getContentEncoding(); | ||
if (contentEncoding != null && contentEncoding.contains("gzip")) { | ||
return unpackGzipEntityString(httpEntity); | ||
} else { | ||
return EntityUtils.toString(httpEntity, getContentEncoding(httpEntity.getContentEncoding())); | ||
} | ||
} catch (IOException | ParseException e) { | ||
throw new AllureResultsWriteException("Can't read request message body to String", e); | ||
} | ||
} | ||
|
||
/** | ||
* Retrieves the content encoding of the HTTP entity. | ||
* | ||
* @param contentEncoding the content encoding value | ||
* @return the charset corresponding to the content encoding, or UTF-8 if the encoding is invalid | ||
*/ | ||
static Charset getContentEncoding(final String contentEncoding) { | ||
try { | ||
return Charset.forName(contentEncoding); | ||
} catch (IllegalArgumentException ignored) { | ||
return StandardCharsets.UTF_8; | ||
} | ||
} | ||
|
||
/** | ||
* Unpacks the GZIP-encoded entity string. | ||
* | ||
* @param entity the GZIP-encoded HTTP entity | ||
* @return the unpacked entity string | ||
* @throws IOException if an error occurs while unpacking the entity | ||
*/ | ||
static String unpackGzipEntityString(final HttpEntity entity) throws IOException { | ||
final GZIPInputStream gis = new GZIPInputStream(entity.getContent()); | ||
final Charset contentEncoding = getContentEncoding(entity.getContentEncoding()); | ||
try (InputStreamReader inputStreamReader = new InputStreamReader(gis, contentEncoding)) { | ||
try (BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { | ||
final StringBuilder outStr = new StringBuilder(); | ||
String line; | ||
while ((line = bufferedReader.readLine()) != null) { | ||
outStr.append(line); | ||
} | ||
return outStr.toString(); | ||
} | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.