• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

smartsheet / smartsheet-java-sdk / #55

02 Oct 2024 07:40PM UTC coverage: 60.548% (+0.7%) from 59.836%
#55

push

github

web-flow
Release prep for 3.2.1 (#103)

4156 of 6864 relevant lines covered (60.55%)

0.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.98
/src/main/java/com/smartsheet/api/internal/http/DefaultHttpClient.java
1
/*
2
 * Copyright (C) 2024 Smartsheet
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.smartsheet.api.internal.http;
18

19
import com.smartsheet.api.Trace;
20
import com.smartsheet.api.internal.json.JacksonJsonSerializer;
21
import com.smartsheet.api.internal.json.JsonSerializer;
22
import com.smartsheet.api.internal.util.StreamUtil;
23
import com.smartsheet.api.internal.util.Util;
24
import com.smartsheet.api.models.Error;
25
import org.apache.http.Header;
26
import org.apache.http.NoHttpResponseException;
27
import org.apache.http.client.ClientProtocolException;
28
import org.apache.http.client.NonRepeatableRequestException;
29
import org.apache.http.client.config.RequestConfig;
30
import org.apache.http.client.methods.CloseableHttpResponse;
31
import org.apache.http.client.methods.HttpDelete;
32
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
33
import org.apache.http.client.methods.HttpGet;
34
import org.apache.http.client.methods.HttpPost;
35
import org.apache.http.client.methods.HttpPut;
36
import org.apache.http.client.methods.HttpRequestBase;
37
import org.apache.http.client.methods.HttpRequestWrapper;
38
import org.apache.http.entity.ContentType;
39
import org.apache.http.entity.InputStreamEntity;
40
import org.apache.http.impl.client.CloseableHttpClient;
41
import org.apache.http.impl.client.HttpClients;
42
import org.apache.http.protocol.BasicHttpContext;
43
import org.apache.http.protocol.HttpContext;
44
import org.jetbrains.annotations.Nullable;
45
import org.slf4j.Logger;
46
import org.slf4j.LoggerFactory;
47

48
import java.io.ByteArrayInputStream;
49
import java.io.IOException;
50
import java.io.InputStream;
51
import java.io.OutputStream;
52
import java.io.PrintWriter;
53
import java.util.HashMap;
54
import java.util.HashSet;
55
import java.util.Map;
56
import java.util.Random;
57
import java.util.Set;
58

59
/**
60
 * This is the Apache HttpClient based HttpClient implementation.
61
 * <p>
62
 * Thread Safety: This class is thread safe because it is immutable and the underlying Apache CloseableHttpClient is
63
 * thread safe.
64
 *
65
 * @see <a href="http://hc.apache.org/httpcomponents-client-ga/index.html">Apache HttpClient</a>
66
 */
67
public class DefaultHttpClient implements HttpClient {
68

69
    /**
70
     * logger for general errors, warnings, etc
71
     */
72
    protected static final Logger logger = LoggerFactory.getLogger(DefaultHttpClient.class);
1✔
73

74
    /**
75
     * used by default retry/timeout logic and available for overriders
76
     */
77
    protected static final String JSON_MIME_TYPE = ContentType.APPLICATION_JSON.getMimeType();
1✔
78

79
    protected JsonSerializer jsonSerializer;
80

81
    protected long maxRetryTimeMillis = 15000;
1✔
82

83
    /**
84
     * Represents the underlying Apache CloseableHttpClient.
85
     * <p>
86
     * It will be initialized in constructor and will not change afterwards.
87
     */
88
    private final CloseableHttpClient httpClient;
89

90
    /**
91
     * The apache http response.
92
     */
93
    private CloseableHttpResponse apacheHttpResponse;
94

95
    /**
96
     * to avoid creating new sets for each call (we use Sets for practical and perf reasons)
97
     */
98
    private static final Set<Trace> REQUEST_RESPONSE_SUMMARY = Set.of(
1✔
99
            Trace.RequestHeaders,
100
            Trace.RequestBodySummary,
101
            Trace.ResponseHeaders,
102
            Trace.ResponseBodySummary
103
    );
104

105
    /**
106
     * default values for trace-logging extracted from system-properties (can still be overwritten at the instance level)
107
     */
108
    private static final boolean TRACE_PRETTY_PRINT_DEFAULT = Boolean.parseBoolean(System.getProperty("Smartsheet.trace.pretty", "true"));
1✔
109

110
    // empty by default
111
    private static final Set<Trace> TRACE_DEFAULT_TRACE_SET = Trace.parse(System.getProperty("Smartsheet.trace.parts"));
1✔
112

113
    /**
114
     * where to send trace logs
115
     */
116
    private static PrintWriter traceWriter;
117

118
    private final Random random = new Random();
1✔
119

120
    static {
121
        // default trace stream
122
        setTraceStream(System.out);
1✔
123
        if (!TRACE_DEFAULT_TRACE_SET.isEmpty()) {
1✔
124
            traceWriter.println("default trace logging - pretty:" + TRACE_PRETTY_PRINT_DEFAULT + " parts:" + TRACE_DEFAULT_TRACE_SET);
×
125
        }
126
    }
1✔
127

128
    /**
129
     * the set of Trace levels to use in trace-logging
130
     */
131
    private final Set<Trace> traces = new HashSet<>(TRACE_DEFAULT_TRACE_SET);
1✔
132

133
    /**
134
     * whether to log pretty or compact
135
     */
136
    private boolean tracePrettyPrint = TRACE_PRETTY_PRINT_DEFAULT;
1✔
137

138
    private static final String LOG_ARG = "{}";
139
    private static final String ERROR_OCCURRED = "Error occurred.";
140

141
    /**
142
     * Constructor.
143
     */
144
    public DefaultHttpClient() {
145
        this(HttpClients.createDefault(), new JacksonJsonSerializer());
1✔
146
    }
1✔
147

148
    /**
149
     * Constructor.
150
     * <p>
151
     * Parameters: - httpClient : the Apache CloseableHttpClient to use
152
     * <p>
153
     * Exceptions: - IllegalArgumentException : if any argument is null
154
     *
155
     * @param httpClient the http client
156
     */
157
    public DefaultHttpClient(CloseableHttpClient httpClient, JsonSerializer jsonSerializer) {
1✔
158
        this.httpClient = Util.throwIfNull(httpClient);
1✔
159
        this.jsonSerializer = jsonSerializer;
1✔
160
    }
1✔
161

162
    /**
163
     * Log to the SLF4J logger (level based upon response status code). Override this function to add logging
164
     * or capture performance metrics.
165
     *
166
     * @param request        request
167
     * @param requestEntity  request body
168
     * @param response       response
169
     * @param responseEntity response body
170
     * @param durationMillis response time in ms
171
     */
172
    public void logRequest(HttpRequestBase request, HttpEntitySnapshot requestEntity,
173
                           HttpResponse response, HttpEntitySnapshot responseEntity, long durationMillis) throws IOException {
174

175
        logger.info("{} {}, Response Code:{}, Request completed in {} ms", request.getMethod(), request.getURI(),
1✔
176
                response.getStatusCode(), durationMillis);
1✔
177
        logger.debug(LOG_ARG, RequestAndResponseData.of(request, requestEntity, response, responseEntity, REQUEST_RESPONSE_SUMMARY));
1✔
178
    }
1✔
179

180
    /**
181
     * Make an HTTP request and return the response.
182
     *
183
     * @param smartsheetRequest the smartsheet request
184
     * @return the HTTP response
185
     * @throws HttpClientException the HTTP client exception
186
     */
187
    public HttpResponse request(HttpRequest smartsheetRequest) throws HttpClientException {
188
        Util.throwIfNull(smartsheetRequest);
1✔
189
        if (smartsheetRequest.getUri() == null) {
1✔
190
            throw new IllegalArgumentException("A Request URI is required.");
1✔
191
        }
192

193
        long start = System.currentTimeMillis();
1✔
194

195
        HttpRequestBase apacheHttpRequest;
196
        HttpResponse smartsheetResponse;
197

198
        InputStream bodyStream = null;
1✔
199
        if (smartsheetRequest.getEntity() != null && smartsheetRequest.getEntity().getContent() != null) {
1✔
200
            bodyStream = smartsheetRequest.getEntity().getContent();
1✔
201
        }
202
        // the retry logic will consume the body stream so we make sure it supports mark/reset and mark it
203
        boolean canRetryRequest = bodyStream == null || bodyStream.markSupported();
1✔
204
        if (!canRetryRequest) {
1✔
205
            try {
206
                // attempt to wrap the body stream in a input-stream that does support mark/reset
207
                bodyStream = new ByteArrayInputStream(StreamUtil.readBytesFromStream(bodyStream));
1✔
208
                // close the old stream (just to be tidy) and then replace it with a reset-able stream
209
                smartsheetRequest.getEntity().getContent().close();
1✔
210
                smartsheetRequest.getEntity().setContent(bodyStream);
1✔
211
                canRetryRequest = true;
1✔
212
            } catch (IOException ignore) {
1✔
213
            }
1✔
214
        }
215

216
        // the retry loop
217
        while (true) {
218
            int attempt = 0;
1✔
219
            apacheHttpRequest = createApacheRequest(smartsheetRequest);
1✔
220

221
            // Set HTTP headers
222
            if (smartsheetRequest.getHeaders() != null) {
1✔
223
                for (Map.Entry<String, String> header : smartsheetRequest.getHeaders().entrySet()) {
1✔
224
                    apacheHttpRequest.addHeader(header.getKey(), header.getValue());
1✔
225
                }
1✔
226
            }
227

228
            HttpEntitySnapshot responseEntityCopy = null;
1✔
229
            // Set HTTP entity
230
            HttpEntitySnapshot requestEntityCopy = copyRequestEntity(smartsheetRequest, apacheHttpRequest);
1✔
231

232
            // mark the body so we can reset on retry
233
            if (canRetryRequest && bodyStream != null) {
1✔
234
                bodyStream.mark((int) smartsheetRequest.getEntity().getContentLength());
1✔
235
            }
236

237
            // Make the HTTP request
238
            smartsheetResponse = new HttpResponse();
1✔
239
            HttpContext context = new BasicHttpContext();
1✔
240
            try {
241
                long startTime = System.currentTimeMillis();
1✔
242
                apacheHttpResponse = this.httpClient.execute(apacheHttpRequest, context);
1✔
243
                long endTime = System.currentTimeMillis();
1✔
244

245
                HttpEntitySnapshot newResponseCopy = updateWithResponse(apacheHttpRequest, context, smartsheetResponse);
1✔
246
                if (newResponseCopy != null) {
1✔
247
                    responseEntityCopy = newResponseCopy;
1✔
248
                }
249

250
                long responseTime = endTime - startTime;
1✔
251
                logRequest(apacheHttpRequest, requestEntityCopy, smartsheetResponse, responseEntityCopy, responseTime);
1✔
252

253
                // trace-logging of request and response (if so configured)
254
                if (traces.size() > 0) {
1✔
255
                    RequestAndResponseData requestAndResponseData = RequestAndResponseData.of(apacheHttpRequest,
1✔
256
                            requestEntityCopy, smartsheetResponse, responseEntityCopy, traces);
257
                    traceWriter.println(requestAndResponseData.toString(tracePrettyPrint));
1✔
258
                }
259

260
                if (smartsheetResponse.getStatusCode() == 200) {
1✔
261
                    // call successful, exit the retry loop
262
                    break;
1✔
263
                }
264

265
                // the retry logic might consume the content stream so we make sure it supports mark/reset and mark it
266
                InputStream contentStream = smartsheetResponse.getEntity().getContent();
1✔
267
                if (!contentStream.markSupported()) {
1✔
268
                    // wrap the response stream in a input-stream that does support mark/reset
269
                    contentStream = new ByteArrayInputStream(StreamUtil.readBytesFromStream(contentStream));
1✔
270
                    // close the old stream (just to be tidy) and then replace it with a reset-able stream
271
                    smartsheetResponse.getEntity().getContent().close();
1✔
272
                    smartsheetResponse.getEntity().setContent(contentStream);
1✔
273
                }
274
                try {
275
                    contentStream.mark((int) smartsheetResponse.getEntity().getContentLength());
1✔
276
                    long timeSpent = System.currentTimeMillis() - start;
1✔
277
                    if (!shouldRetry(++attempt, timeSpent, smartsheetResponse)) {
1✔
278
                        // should not retry, or retry time exceeded, exit the retry loop
279
                        break;
280
                    }
281
                } finally {
282
                    if (bodyStream != null) {
1✔
283
                        bodyStream.reset();
1✔
284
                    }
285
                    contentStream.reset();
1✔
286
                }
287
                // moving this to finally causes issues because socket is closed (which means response stream is closed)
288
                this.releaseConnection();
×
289

290
            } catch (ClientProtocolException e) {
1✔
291
                logger.warn("ClientProtocolException " + e.getMessage());
1✔
292
                logger.warn(LOG_ARG, RequestAndResponseData.of(apacheHttpRequest, requestEntityCopy, smartsheetResponse,
1✔
293
                        responseEntityCopy, REQUEST_RESPONSE_SUMMARY));
294
                try {
295
                    // if this is a PUT and was retried by the http client, the body content stream is at the
296
                    // end and is a NonRepeatableRequest. If we marked the body content stream prior to execute,
297
                    // reset and retry
298
                    if (canRetryRequest && e.getCause() instanceof NonRepeatableRequestException) {
1✔
299
                        if (smartsheetRequest.getEntity() != null) {
×
300
                            smartsheetRequest.getEntity().getContent().reset();
×
301
                        }
302
                        continue;
×
303
                    }
304
                } catch (IOException ignore) {
×
305
                }
1✔
306
                throw new HttpClientException(ERROR_OCCURRED, e);
1✔
307
            } catch (NoHttpResponseException e) {
×
308
                logger.warn("NoHttpResponseException {}", e.getMessage());
×
309
                logger.warn(LOG_ARG, RequestAndResponseData.of(apacheHttpRequest, requestEntityCopy, smartsheetResponse,
×
310
                        responseEntityCopy, REQUEST_RESPONSE_SUMMARY));
311
                try {
312
                    // check to see if the response was empty and this was a POST. All other HTTP methods
313
                    // will be automatically retried by the http client.
314
                    // (POST is non-idempotent and is not retried automatically, but is safe for us to retry)
315
                    if (canRetryRequest && smartsheetRequest.getMethod() == HttpMethod.POST) {
×
316
                        if (smartsheetRequest.getEntity() != null) {
×
317
                            smartsheetRequest.getEntity().getContent().reset();
×
318
                        }
319
                        continue;
×
320
                    }
321
                } catch (IOException ignore) {
×
322
                }
×
323
                throw new HttpClientException(ERROR_OCCURRED, e);
×
324
            } catch (IOException e) {
1✔
325
                logger.warn(LOG_ARG, RequestAndResponseData.of(apacheHttpRequest, requestEntityCopy, smartsheetResponse,
1✔
326
                        responseEntityCopy, REQUEST_RESPONSE_SUMMARY));
327
                throw new HttpClientException(ERROR_OCCURRED, e);
1✔
328
            }
×
329
        }
×
330
        return smartsheetResponse;
1✔
331
    }
332

333
    private HttpEntitySnapshot copyRequestEntity(HttpRequest smartsheetRequest, HttpRequestBase apacheHttpRequest) {
334
        final HttpEntity entity = smartsheetRequest.getEntity();
1✔
335
        HttpEntitySnapshot requestEntityCopy = null;
1✔
336
        if (apacheHttpRequest instanceof HttpEntityEnclosingRequestBase && entity != null && entity.getContent() != null) {
1✔
337
            try {
338
                // we need access to the original request stream so we can log it (in the event of errors and/or tracing)
339
                requestEntityCopy = new HttpEntitySnapshot(entity);
1✔
340
            } catch (IOException iox) {
×
341
                logger.error("failed to make copy of original request entity", iox);
×
342
            }
1✔
343

344
            InputStreamEntity streamEntity = new InputStreamEntity(entity.getContent(), entity.getContentLength());
1✔
345
            // why?  not supported by library?
346
            streamEntity.setChunked(false);
1✔
347
            ((HttpEntityEnclosingRequestBase) apacheHttpRequest).setEntity(streamEntity);
1✔
348
        }
349
        return requestEntityCopy;
1✔
350
    }
351

352
    @Nullable
353
    private HttpEntitySnapshot updateWithResponse(HttpRequestBase apacheHttpRequest, HttpContext context,
354
                                                  HttpResponse smartsheetResponse) throws IOException {
355
        // Set request headers to values ACTUALLY SENT (not just created by us), this would include:
356
        // 'Connection', 'Accept-Encoding', etc. However, if a proxy is used, this may be the proxy's CONNECT
357
        // request, hence the test for HTTP method first
358
        Object httpRequest = context.getAttribute("http.request");
1✔
359
        if (httpRequest != null && HttpRequestWrapper.class.isAssignableFrom(httpRequest.getClass())) {
1✔
360
            HttpRequestWrapper actualRequest = (HttpRequestWrapper) httpRequest;
1✔
361
            switch (HttpMethod.valueOf(actualRequest.getMethod())) {
1✔
362
                case GET:
363
                case POST:
364
                case PUT:
365
                case DELETE:
366
                    apacheHttpRequest.setHeaders(actualRequest.getAllHeaders());
1✔
367
                    break;
1✔
368
                default:
369
                    break;
370
            }
371
        }
372

373
        // Set returned headers
374
        smartsheetResponse.setHeaders(new HashMap<>());
1✔
375
        for (Header header : apacheHttpResponse.getAllHeaders()) {
1✔
376
            smartsheetResponse.getHeaders().put(header.getName(), header.getValue());
1✔
377
        }
378
        smartsheetResponse.setStatus(apacheHttpResponse.getStatusLine().getStatusCode(),
1✔
379
                apacheHttpResponse.getStatusLine().toString());
1✔
380

381
        // Set returned entities
382
        if (apacheHttpResponse.getEntity() != null) {
1✔
383
            HttpEntity httpEntity = new HttpEntity();
1✔
384
            httpEntity.setContentType(apacheHttpResponse.getEntity().getContentType().getValue());
1✔
385
            httpEntity.setContentLength(apacheHttpResponse.getEntity().getContentLength());
1✔
386
            httpEntity.setContent(apacheHttpResponse.getEntity().getContent());
1✔
387
            smartsheetResponse.setEntity(httpEntity);
1✔
388
            return new HttpEntitySnapshot(httpEntity);
1✔
389
        }
390

391
        return null;
×
392
    }
393

394
    /**
395
     * Create the Apache HTTP request. Override this function to inject additional
396
     * haaders in the request or use a proxy.
397
     *
398
     * @param smartsheetRequest (request method and base URI come from here)
399
     * @return the Apache HTTP request
400
     */
401
    public HttpRequestBase createApacheRequest(HttpRequest smartsheetRequest) {
402
        HttpRequestBase apacheHttpRequest;
403

404
        // Create Apache HTTP request based on the smartsheetRequest request type
405
        switch (smartsheetRequest.getMethod()) {
1✔
406
            case GET:
407
                apacheHttpRequest = new HttpGet(smartsheetRequest.getUri());
1✔
408
                break;
1✔
409
            case POST:
410
                apacheHttpRequest = new HttpPost(smartsheetRequest.getUri());
1✔
411
                break;
1✔
412
            case PUT:
413
                apacheHttpRequest = new HttpPut(smartsheetRequest.getUri());
1✔
414
                break;
1✔
415
            case DELETE:
416
                apacheHttpRequest = new HttpDelete(smartsheetRequest.getUri());
1✔
417
                break;
1✔
418
            default:
419
                throw new UnsupportedOperationException("Request method " + smartsheetRequest.getMethod() + " is not supported!");
×
420
        }
421

422
        RequestConfig.Builder builder = RequestConfig.custom();
1✔
423
        if (apacheHttpRequest.getConfig() != null) {
1✔
424
            builder = RequestConfig.copy(apacheHttpRequest.getConfig());
×
425
        }
426
        builder.setRedirectsEnabled(true);
1✔
427
        RequestConfig config = builder.build();
1✔
428
        apacheHttpRequest.setConfig(config);
1✔
429
        return apacheHttpRequest;
1✔
430
    }
431

432
    /**
433
     * Set the max retry time for API calls which fail and are retry-able.
434
     */
435
    public void setMaxRetryTimeMillis(long maxRetryTimeMillis) {
436
        this.maxRetryTimeMillis = maxRetryTimeMillis;
×
437
    }
×
438

439
    /**
440
     * The backoff calculation routine. Uses exponential backoff. If the maximum elapsed time
441
     * has expired, this calculation returns -1 causing the caller to fall out of the retry loop.
442
     *
443
     * @return -1 to fall out of retry loop, positive number indicates backoff time
444
     */
445
    public long calcBackoff(int previousAttempts, long totalElapsedTimeMillis, Error error) {
446

447
        long backoffMillis = (long) (Math.pow(2, previousAttempts) * 1000) + random.nextInt(1000);
×
448

449
        if (totalElapsedTimeMillis + backoffMillis > maxRetryTimeMillis) {
×
450
            logger.info(
×
451
                    "Elapsed time {} + backoff time {} exceeds max retry time {}, exiting retry loop",
452
                    totalElapsedTimeMillis,
×
453
                    backoffMillis,
×
454
                    maxRetryTimeMillis
×
455
            );
456
            return -1;
×
457
        }
458
        return backoffMillis;
×
459
    }
460

461
    /**
462
     * Called when an API request fails to determine if it can retry the request.
463
     * Calls calcBackoff to determine the time to wait in between retries.
464
     *
465
     * @param previousAttempts       number of attempts (including this one) to execute request
466
     * @param totalElapsedTimeMillis total time spent in millis for all previous (and this) attempt
467
     * @param response               the failed HttpResponse
468
     * @return true if this request can be retried
469
     */
470
    public boolean shouldRetry(int previousAttempts, long totalElapsedTimeMillis, HttpResponse response) {
471
        String contentType = response.getEntity().getContentType();
1✔
472
        if (contentType != null && !contentType.startsWith(JSON_MIME_TYPE)) {
1✔
473
            // it's not JSON; don't even try to parse it
474
            return false;
1✔
475
        }
476
        Error error;
477
        try {
478
            error = jsonSerializer.deserialize(Error.class, response.getEntity().getContent());
1✔
479
        } catch (IOException e) {
×
480
            return false;
×
481
        }
1✔
482
        switch (error.getErrorCode()) {
1✔
483
            case 4001:
484
                // Smartsheet.com is currently offline for system maintenance. Please check back again shortly.
485
            case 4002:
486
                // Server timeout exceeded. Request has failed
487
            case 4003:
488
                // Rate limit exceeded.
489
            case 4004:
490
                // An unexpected error has occurred. Please retry your request
491
                // If you encounter this error repeatedly, please contact api@smartsheet.com for assistance
492
                break;
×
493
            default:
494
                return false;
1✔
495
        }
496

497
        long backoffMillis = calcBackoff(previousAttempts, totalElapsedTimeMillis, error);
×
498
        if (backoffMillis < 0) {
×
499
            return false;
×
500
        }
501

502
        logger.info("HttpError StatusCode={}: Retrying in {} milliseconds", response.getStatusCode(), backoffMillis);
×
503
        try {
504
            Thread.sleep(backoffMillis);
×
505
        } catch (InterruptedException e) {
×
506
            logger.warn("sleep interrupted", e);
×
507
            return false;
×
508
        }
×
509
        return true;
×
510
    }
511

512
    /**
513
     * Close the HttpClient.
514
     *
515
     * @throws IOException Signals that an I/O exception has occurred.
516
     */
517
    @Override
518
    public void close() throws IOException {
519
        this.httpClient.close();
1✔
520
    }
1✔
521

522
    /* (non-Javadoc)
523
     * @see com.smartsheet.api.internal.http.HttpClient#releaseConnection()
524
     */
525
    @Override
526
    public void releaseConnection() {
527
        if (apacheHttpResponse != null) {
1✔
528
            try {
529
                apacheHttpResponse.close();
1✔
530
                apacheHttpResponse = null;
1✔
531
            } catch (IOException e) {
×
532
                logger.error("error closing Apache HttpResponse", e);
×
533
            }
1✔
534
        }
535
    }
1✔
536

537
    /**
538
     * set the traces for this client
539
     *
540
     * @param traces the fields to include in trace-logging
541
     */
542
    public void setTraces(Trace... traces) {
543
        this.traces.clear();
1✔
544
        for (Trace trace : traces) {
1✔
545
            if (!trace.addReplacements(this.traces)) {
1✔
546
                this.traces.add(trace);
×
547
            }
548
        }
549
    }
1✔
550

551
    /**
552
     * set whether to use nicely-formatted JSON or more compact format JSON in trace logging
553
     *
554
     * @param pretty whether to print JSON in a "pretty" format or compact
555
     */
556
    public void setTracePrettyPrint(boolean pretty) {
557
        tracePrettyPrint = pretty;
×
558
    }
×
559

560
    /**
561
     * only included for testing purposes
562
     */
563
    public static void setTraceStream(OutputStream traceStream) {
564
        traceWriter = new PrintWriter(traceStream, true);
1✔
565
    }
1✔
566
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc