Skip to content
Merged
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 @@ -19,6 +19,9 @@
*/
public class VideoStreamingHttpClient extends HTTPHC4Impl {

private static final String NON_HTTP_RESPONSE_CODE = "Non HTTP response code";
private static final String NON_HTTP_RESPONSE_MESSAGE = "Non HTTP response message";

private transient volatile boolean interrupted = false;

private Map<String, String> headers = new HashMap<>();
Expand Down Expand Up @@ -53,13 +56,35 @@ public HTTPSampleResult downloadUri(URI uri) {
if (interrupted) {
throw new SamplerInterruptedException();
}
if (uri == null || !uri.isAbsolute()) {
return buildInvalidUriResult(uri, "URI is not absolute");
}
try {
return sample(uri.toURL(), "GET", false, 0);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
return buildInvalidUriResult(uri, e.getMessage());
}
}

/*
When the resolved URI can't be turned into an absolute URL (e.g. a dynamic master URL
variable resolved to an empty value or a relative path), we return a failed sample result
instead of throwing an uncaught IllegalArgumentException. This way the failure is recorded as
a measurable sample and handled by the normal download error path, rather than aborting the
sampler with a raw stack trace in the logs.
*/
private HTTPSampleResult buildInvalidUriResult(URI uri, String detail) {
HTTPSampleResult result = new HTTPSampleResult();
result.sampleStart();
result.sampleEnd();
result.setSuccessful(false);
result.setResponseCode(
NON_HTTP_RESPONSE_CODE + ": " + IllegalArgumentException.class.getName());
result.setResponseMessage(NON_HTTP_RESPONSE_MESSAGE + ": "
+ detail + " [" + (uri != null ? uri : "null") + "]");
return result;
}

public void addHeader(String name, String value) {
headers.put(name, value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ public SampleResult sample() {
sample(masterUri, baseSampler.getBandwidthSelector(), baseSampler.getResolutionSelector(),
baseSampler.getAudioLanguage(), baseSampler.getSubtitleLanguage(),
baseSampler.getPlaySecondsOrWarn());
} catch (IllegalArgumentException e) {
// the master URL (often a dynamic variable) is not a valid URI reference; record a failed
// sample instead of letting the exception abort the sampler with a raw stack trace
LOG.warn("Invalid master playlist URL '{}'", baseSampler.getMasterUrl(), e);
HTTPSampleResult result = new HTTPSampleResult();
result.sampleStart();
result.sampleEnd();
sampleResultProcessor.accept(buildPlaylistName(MASTER_TYPE_NAME),
baseSampler.errorResult(result, e));
} catch (SamplerInterruptedException e) {
LOG.debug("Sampler interrupted by JMeter", e);
} catch (InterruptedException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.blazemeter.jmeter.videostreaming.core;

import static org.assertj.core.api.Java6Assertions.assertThat;

import com.blazemeter.jmeter.JMeterTestUtils;
import com.blazemeter.jmeter.hls.logic.HlsSampler;
import java.net.URI;
import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class VideoStreamingHttpClientTest {

private VideoStreamingHttpClient httpClient;

@BeforeClass
public static void setupClass() {
JMeterTestUtils.setupJmeterEnv();
}

@Before
public void setup() {
httpClient = new VideoStreamingHttpClient(new HlsSampler());
}

/*
Reproduces the production scenario where a dynamic master URL variable resolves to a value
without a scheme (e.g. the JSON extractor default "C_hlsManifestPath_Not_Found"). Such a value
is a valid relative URI, so URI.create succeeds but URI.toURL() throws "URI is not absolute".
The client must not propagate that exception; it must return a failed sample result.
*/
@Test
public void shouldReturnFailedResultWhenUriIsNotAbsolute() {
HTTPSampleResult result = httpClient.downloadUri(URI.create("C_hlsManifestPath_Not_Found"));
assertFailedResult(result);
}

@Test
public void shouldReturnFailedResultWhenUriIsRelativePath() {
HTTPSampleResult result = httpClient.downloadUri(URI.create("/relative/master.m3u8"));
assertFailedResult(result);
}

@Test
public void shouldReturnFailedResultWhenUriIsNull() {
HTTPSampleResult result = httpClient.downloadUri(null);
assertFailedResult(result);
}

private void assertFailedResult(HTTPSampleResult result) {
assertThat(result).isNotNull();
assertThat(result.isSuccessful()).isFalse();
assertThat(result.getResponseCode()).contains(IllegalArgumentException.class.getName());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -953,4 +953,16 @@ public void shouldFallbackToAtLeastOneSegmentWhenTargetDurationMissing() throws
buildMediaSegmentSampleResult(5));
}

@Test
public void shouldRecordFailedMasterSampleWhenMasterUrlIsNotValidUriReference() {
// unresolved dynamic variable (contains illegal URI characters) so URI.create itself fails
baseSampler.setMasterUrl("${C_hlsManifestPath}");
sampler.sample();
ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<SampleResult> resultCaptor = ArgumentCaptor.forClass(SampleResult.class);
verify(sampleResultProcessor).accept(nameCaptor.capture(), resultCaptor.capture());
assertThat(nameCaptor.getValue()).isEqualTo("master playlist");
assertThat(resultCaptor.getValue().isSuccessful()).isFalse();
}

}