Skip to content

Commit 055ddad

Browse files
feat: Add configurable read timeout
1 parent 13a33cb commit 055ddad

5 files changed

Lines changed: 52 additions & 5 deletions

File tree

src/main/java/com/scanoss/Scanner.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ public class Scanner {
8989
@Builder.Default
9090
private Duration timeout = Duration.ofSeconds(DEFAULT_TIMEOUT); // API POST timeout
9191

92+
@Builder.Default
93+
private Duration readTimeout = Duration.ofSeconds(DEFAULT_READ_TIMEOUT); // API read timeout (0 = disabled)
94+
9295
@Builder.Default
9396
private Integer retryLimit = DEFAULT_HTTP_RETRY_LIMIT; // Retry limit for posting scan requests
9497

@@ -113,7 +116,7 @@ public class Scanner {
113116
@SuppressWarnings("unused")
114117
private Scanner(Boolean skipSnippets, Boolean allExtensions, Boolean obfuscate, Boolean hpsm,
115118
Boolean hiddenFilesFolders, Boolean allFolders, Integer numThreads, Duration timeout,
116-
Integer retryLimit, String url, String apiKey, String scanFlags, String sbomType, String sbom,
119+
Duration readTimeout, Integer retryLimit, String url, String apiKey, String scanFlags, String sbomType, String sbom,
117120
Integer snippetLimit, String customCert, Proxy proxy,
118121
Winnowing winnowing, ScanApi scanApi,
119122
ScanFileProcessor scanFileProcessor, WfpFileProcessor wfpFileProcessor,
@@ -130,6 +133,7 @@ private Scanner(Boolean skipSnippets, Boolean allExtensions, Boolean obfuscate,
130133
this.allFolders = allFolders;
131134
this.numThreads = numThreads;
132135
this.timeout = timeout;
136+
this.readTimeout = readTimeout;
133137
this.retryLimit = retryLimit;
134138
this.url = url;
135139
this.apiKey = apiKey;
@@ -149,7 +153,7 @@ private Scanner(Boolean skipSnippets, Boolean allExtensions, Boolean obfuscate,
149153
.skipHeadersLimit(fileSnippetConfig != null && fileSnippetConfig.getSkipHeadersLimit() != null ? fileSnippetConfig.getSkipHeadersLimit() : 0)
150154
.build());
151155
this.scanApi = Objects.requireNonNullElseGet(scanApi, () ->
152-
ScanApi.builder().url(url).apiKey(this.apiKey).timeout(timeout).retryLimit(retryLimit).flags(scanFlags)
156+
ScanApi.builder().url(url).apiKey(this.apiKey).timeout(timeout).readTimeout(readTimeout).retryLimit(retryLimit).flags(scanFlags)
153157
.sbomType(sbomType).sbom(sbom).customCert(customCert).proxy(proxy).settings(this.settings)
154158
.build());
155159
this.scanFileProcessor = Objects.requireNonNullElseGet(scanFileProcessor, () ->

src/main/java/com/scanoss/ScanossConstants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ private ScanossConstants() {
4242
* Default timeout for HTTP communication
4343
*/
4444
public static final int DEFAULT_TIMEOUT = 120;
45+
/**
46+
* Default read timeout for HTTP communication (0 = no read timeout; overall call is still bounded by DEFAULT_TIMEOUT)
47+
*/
48+
public static final int DEFAULT_READ_TIMEOUT = 0;
4549
/**
4650
* Default number of worker threads to use then processing files
4751
*/

src/main/java/com/scanoss/cli/ScanCommandLine.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ class ScanCommandLine implements Runnable {
8585
@picocli.CommandLine.Option(names = {"-M", "--timeout"}, description = "Timeout (in seconds) for API communication (optional - default " + DEFAULT_TIMEOUT + ")")
8686
private int timeoutLimit = DEFAULT_TIMEOUT;
8787

88+
@picocli.CommandLine.Option(names = {"--read-timeout"}, description = "Read timeout (in seconds) for API communication (optional - default " + DEFAULT_READ_TIMEOUT + ", 0 = disabled)")
89+
private int readTimeoutLimit = DEFAULT_READ_TIMEOUT;
90+
8891
@picocli.CommandLine.Option(names = {"-F", "--flags"}, description = "Scanning engine flags (1: disable snippet matching, 2 enable snippet ids, 4: disable dependencies, 8: disable licenses, 16: disable copyrights, 32: disable vulnerabilities, 64: disable quality, 128: disable cryptography,256: disable best match only, 512: hide identified files, 1024: enable download_url, 2048: enable GitHub full path, 4096: disable extended server stats")
8992
private String scanFlags;
9093

@@ -198,7 +201,7 @@ public void run() {
198201
}
199202
scanner = Scanner.builder().skipSnippets(skipSnippets).allFolders(allFolders).allExtensions(allExtensions)
200203
.hiddenFilesFolders(allHidden).numThreads(numThreads).url(apiUrl).apiKey(apiKey)
201-
.retryLimit(retryLimit).timeout(Duration.ofSeconds(timeoutLimit)).scanFlags(scanFlags)
204+
.retryLimit(retryLimit).timeout(Duration.ofSeconds(timeoutLimit)).readTimeout(Duration.ofSeconds(readTimeoutLimit)).scanFlags(scanFlags)
202205
.snippetLimit(snippetLimit).customCert(caCertPem).proxy(proxy).hpsm(enableHpsm)
203206
.settings(settings).obfuscate(obfuscate)
204207
.build();

src/main/java/com/scanoss/rest/ScanApi.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.util.concurrent.TimeUnit;
4646

4747
import static com.scanoss.ScanossConstants.DEFAULT_HTTP_RETRY_LIMIT;
48+
import static com.scanoss.ScanossConstants.DEFAULT_READ_TIMEOUT;
4849
import static com.scanoss.ScanossConstants.DEFAULT_TIMEOUT;
4950

5051
/**
@@ -62,6 +63,8 @@ public class ScanApi {
6263
@Builder.Default
6364
private Duration timeout = Duration.ofSeconds(DEFAULT_TIMEOUT); // API POST timeout
6465
@Builder.Default
66+
private Duration readTimeout = Duration.ofSeconds(DEFAULT_READ_TIMEOUT); // API read timeout (0 = disabled, inherits call timeout)
67+
@Builder.Default
6568
private Integer retryLimit = DEFAULT_HTTP_RETRY_LIMIT; // Retry limit for posting scan requests
6669
private String url; // SCANOSS API URI
6770
private String apiKey; // SCANOSS premium API key
@@ -75,13 +78,14 @@ public class ScanApi {
7578
private String baseUrl; // SCANOSS base API URI (to used instead of url)
7679
private ScanossSettings settings;
7780
@SuppressWarnings("unused")
78-
private ScanApi(String scanType, Duration timeout, Integer retryLimit, String url, String apiKey, String flags,
81+
private ScanApi(String scanType, Duration timeout, Duration readTimeout, Integer retryLimit, String url, String apiKey, String flags,
7982
String sbomType, String sbom,
8083
OkHttpClient okHttpClient, Map<String, String> headers, String customCert,
8184
Proxy proxy, String baseUrl, ScanossSettings settings) {
8285
this.settings = settings;
8386
this.scanType = scanType;
8487
this.timeout = timeout;
88+
this.readTimeout = readTimeout;
8589
this.retryLimit = retryLimit;
8690
this.url = url;
8791
this.apiKey = resolveApiKey(apiKey);
@@ -99,7 +103,8 @@ private ScanApi(String scanType, Duration timeout, Integer retryLimit, String ur
99103
}
100104
if (okHttpClient == null) {
101105
OkHttpClient.Builder okBuilder = new OkHttpClient.Builder();
102-
okBuilder.callTimeout(this.timeout); // Set default timeout
106+
okBuilder.callTimeout(this.timeout); // Set default call timeout
107+
okBuilder.readTimeout(this.readTimeout != null ? this.readTimeout : Duration.ZERO); // 0 = no read timeout, relies on callTimeout
103108
// Build the HTTP client with a custom certificate (ignoring hostname verification)
104109
if (customCert != null && ! customCert.isEmpty()) {
105110
HandshakeCertificates certificates = new HandshakeCertificates.Builder()

src/test/java/com/scanoss/TestScanApi.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,37 @@ public void TestScanApiScanTimeoutNegative() {
200200
log.info("Finished {} -->", methodName);
201201
}
202202

203+
@Test
204+
public void TestScanApiReadTimeoutDisabled() {
205+
String methodName = new Object() {
206+
}.getClass().getEnclosingMethod().getName();
207+
log.info("<-- Starting {}", methodName);
208+
209+
// readTimeout = 0 (default) should produce an OkHttpClient with readTimeoutMillis == 0 (disabled)
210+
ScanApi scanApi = ScanApi.builder().url(server.url("/api/scan/direct").toString()).build();
211+
assertEquals("Read timeout should be 0 (disabled) by default",
212+
0, scanApi.getOkHttpClient().readTimeoutMillis());
213+
214+
log.info("Finished {} -->", methodName);
215+
}
216+
217+
@Test
218+
public void TestScanApiReadTimeoutConfigured() {
219+
String methodName = new Object() {
220+
}.getClass().getEnclosingMethod().getName();
221+
log.info("<-- Starting {}", methodName);
222+
223+
// readTimeout = 30s should be reflected in the underlying OkHttpClient
224+
ScanApi scanApi = ScanApi.builder()
225+
.url(server.url("/api/scan/direct").toString())
226+
.readTimeout(Duration.ofSeconds(30))
227+
.build();
228+
assertEquals("Read timeout should be 30 000 ms",
229+
30_000, scanApi.getOkHttpClient().readTimeoutMillis());
230+
231+
log.info("Finished {} -->", methodName);
232+
}
233+
203234
@Test
204235
public void TestScanApiTemplate() {
205236
String methodName = new Object() {

0 commit comments

Comments
 (0)