Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import okhttp3.Response;

import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;

/**
Expand Down Expand Up @@ -54,25 +53,40 @@ public HeadersInspectionHandler(@Nonnull final HeadersInspectionOption options)
Scope scope = null;
if (span != null) {
scope = span.makeCurrent();
span.setAttribute("com.microsoft.kiota.handler.headersInspection.enable", true);
span.setAttribute(
"com.microsoft.kiota.handler.headersInspection.enable",
inspectionOption.getInspectRequestHeaders()
|| inspectionOption.getInspectResponseHeaders());
}
try {
if (span != null) {
request = request.newBuilder().tag(Span.class, span).build();
}
if (inspectionOption.getInspectRequestHeaders()) {
for (final Pair<? extends String, ? extends String> header : request.headers()) {
HashSet<String> value = new HashSet<>();
value.add(header.getSecond());
inspectionOption.getRequestHeaders().put(header.getFirst(), value);
synchronized (inspectionOption) {
inspectionOption.getRequestHeaders().clear();
for (final Pair<? extends String, ? extends String> header :
request.headers()) {
inspectionOption
.getRequestHeaders()
.add(header.getFirst(), header.getSecond());
}
}
}
if (inspectionOption.getInspectResponseHeaders()) {
synchronized (inspectionOption) {
inspectionOption.getResponseHeaders().clear();
}
}
Comment thread
baywet marked this conversation as resolved.
final Response response = chain.proceed(request);
if (inspectionOption.getInspectResponseHeaders()) {
for (final Pair<? extends String, ? extends String> header : response.headers()) {
HashSet<String> value = new HashSet<>();
value.add(header.getSecond());
inspectionOption.getResponseHeaders().put(header.getFirst(), value);
synchronized (inspectionOption) {
for (final Pair<? extends String, ? extends String> header :
response.headers()) {
inspectionOption
.getResponseHeaders()
.add(header.getFirst(), header.getSecond());
}
}
}
return response;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.microsoft.kiota.http.middleware;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand All @@ -19,6 +22,14 @@
import org.mockito.stubbing.Answer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

class HeadersInspectionHandlerTest {
private final Chain mockChain;
Expand Down Expand Up @@ -78,4 +89,147 @@ void getsResponseHeaders() throws IOException {
assertEquals(1, option.getResponseHeaders().size());
assertEquals("test", option.getResponseHeaders().get("test").toArray()[0]);
}

@Test
void preservesMultiValuedHeaders() throws IOException {
final HeadersInspectionOption option = new HeadersInspectionOption(true, true);
final HeadersInspectionHandler handler = new HeadersInspectionHandler(option);
final Request request =
new Request.Builder()
.url("http://localhost")
.addHeader("multi", "val1")
.addHeader("multi", "val2")
.build();
when(mockChain.request()).thenReturn(request);
when(mockResponse.headers())
.thenReturn(
new Headers.Builder()
.add("resp-multi", "resp1")
.add("resp-multi", "resp2")
.build());

handler.intercept(mockChain);

assertEquals(1, option.getRequestHeaders().size());
assertEquals(2, option.getRequestHeaders().get("multi").size());
assertTrue(option.getRequestHeaders().get("multi").contains("val1"));
assertTrue(option.getRequestHeaders().get("multi").contains("val2"));

assertEquals(1, option.getResponseHeaders().size());
assertEquals(2, option.getResponseHeaders().get("resp-multi").size());
assertTrue(option.getResponseHeaders().get("resp-multi").contains("resp1"));
assertTrue(option.getResponseHeaders().get("resp-multi").contains("resp2"));
}

@Test
void clearsHeadersBetweenConsecutiveRequests() throws IOException {
final HeadersInspectionOption option = new HeadersInspectionOption(true, true);
final HeadersInspectionHandler handler = new HeadersInspectionHandler(option);

final Request request1 =
new Request.Builder().url("http://localhost").addHeader("first", "val1").build();
when(mockChain.request()).thenReturn(request1);
when(mockResponse.headers())
.thenReturn(new Headers.Builder().add("resp-first", "r1").build());
handler.intercept(mockChain);

assertTrue(option.getRequestHeaders().containsKey("first"));
assertTrue(option.getResponseHeaders().containsKey("resp-first"));

final Request request2 =
new Request.Builder().url("http://localhost").addHeader("second", "val2").build();
when(mockChain.request()).thenReturn(request2);
when(mockResponse.headers())
.thenReturn(new Headers.Builder().add("resp-second", "r2").build());
handler.intercept(mockChain);

assertFalse(option.getRequestHeaders().containsKey("first"));
assertTrue(option.getRequestHeaders().containsKey("second"));
assertFalse(option.getResponseHeaders().containsKey("resp-first"));
assertTrue(option.getResponseHeaders().containsKey("resp-second"));
}

@Test
void clearsResponseHeadersWhenRequestFails() throws IOException {
final HeadersInspectionOption option = new HeadersInspectionOption(true, true);
final HeadersInspectionHandler handler = new HeadersInspectionHandler(option);

final Request successRequest =
new Request.Builder().url("http://localhost").addHeader("first", "val1").build();
when(mockChain.request()).thenReturn(successRequest);
when(mockResponse.headers())
.thenReturn(new Headers.Builder().add("resp-header", "resp-val").build());
handler.intercept(mockChain);

assertEquals(1, option.getResponseHeaders().size());
assertTrue(option.getResponseHeaders().containsKey("resp-header"));

final Chain failingChain = mock(Chain.class);
final Request failingRequest =
new Request.Builder().url("http://localhost").addHeader("second", "val2").build();
when(failingChain.request()).thenReturn(failingRequest);
when(failingChain.proceed(any(Request.class))).thenThrow(new IOException("Network error"));

assertThrows(IOException.class, () -> handler.intercept(failingChain));

assertEquals(0, option.getResponseHeaders().size());
assertEquals(1, option.getRequestHeaders().size());
assertTrue(option.getRequestHeaders().containsKey("second"));
}

@Test
void handlesConcurrentCallsWithoutRaces() throws InterruptedException, ExecutionException {
final HeadersInspectionOption option = new HeadersInspectionOption(true, true);
final HeadersInspectionHandler handler = new HeadersInspectionHandler(option);
final int threadCount = 8;
final int iterationsPerThread = 20;
final ExecutorService executor = Executors.newFixedThreadPool(threadCount);
boolean terminated = false;
try {
final List<Callable<Void>> tasks = new ArrayList<>();

for (int i = 0; i < threadCount; i++) {
final int threadId = i;
tasks.add(
() -> {
for (int j = 0; j < iterationsPerThread; j++) {
final Request req =
new Request.Builder()
.url("http://localhost")
.addHeader("header-" + threadId, "val-" + j)
.addHeader("shared", "s-" + threadId)
.build();
final Chain chain = mock(Chain.class);
final Response resp = mock(Response.class);
when(resp.headers())
.thenReturn(
new Headers.Builder()
.add("resp-" + threadId, "rval-" + j)
.add("resp-shared", "rs-" + threadId)
.build());
when(chain.request()).thenReturn(req);
when(chain.proceed(any(Request.class))).thenReturn(resp);

handler.intercept(chain);
}
return null;
});
}

final List<Future<Void>> futures = executor.invokeAll(tasks);
for (final Future<Void> future : futures) {
future.get();
}
} finally {
executor.shutdown();
terminated = executor.awaitTermination(5, TimeUnit.SECONDS);
if (!terminated) {
executor.shutdownNow();
}
}
assertTrue(terminated);

assertNotNull(option.getRequestHeaders());
assertNotNull(option.getResponseHeaders());
}
}
Loading