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

box / box-java-sdk / #5879

17 Dec 2025 04:15PM UTC coverage: 12.895% (-0.008%) from 12.903%
#5879

Pull #1641

github

web-flow
Merge 9a2ba2654 into e14e97bb7
Pull Request #1641: fix(boxsdkgen): replace internal links with absolute links (box/box-openapi#570)

8368 of 64891 relevant lines covered (12.9%)

0.13 hits per line

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

0.0
/src/main/java/com/box/sdkgen/networking/boxnetworkclient/BoxNetworkClient.java
1
package com.box.sdkgen.networking.boxnetworkclient;
2

3
import static com.box.sdkgen.box.BoxConstants.USER_AGENT_HEADER;
4
import static com.box.sdkgen.box.BoxConstants.X_BOX_UA_HEADER;
5
import static com.box.sdkgen.internal.utils.UtilsManager.readByteStream;
6
import static com.box.sdkgen.serialization.json.JsonManager.jsonToSerializedData;
7
import static com.box.sdkgen.serialization.json.JsonManager.sdToJson;
8
import static com.box.sdkgen.serialization.json.JsonManager.sdToUrlParams;
9
import static java.util.Collections.singletonList;
10
import static okhttp3.ConnectionSpec.MODERN_TLS;
11

12
import com.box.sdkgen.box.errors.BoxAPIError;
13
import com.box.sdkgen.box.errors.BoxSDKError;
14
import com.box.sdkgen.internal.logging.DataSanitizer;
15
import com.box.sdkgen.networking.fetchoptions.FetchOptions;
16
import com.box.sdkgen.networking.fetchoptions.MultipartItem;
17
import com.box.sdkgen.networking.fetchoptions.ResponseFormat;
18
import com.box.sdkgen.networking.fetchresponse.FetchResponse;
19
import com.box.sdkgen.networking.network.NetworkSession;
20
import com.box.sdkgen.networking.networkclient.NetworkClient;
21
import com.box.sdkgen.networking.proxyconfig.ProxyConfig;
22
import com.fasterxml.jackson.databind.JsonNode;
23
import java.io.IOException;
24
import java.net.InetSocketAddress;
25
import java.net.Proxy;
26
import java.net.URI;
27
import java.nio.charset.StandardCharsets;
28
import java.util.Locale;
29
import java.util.Map;
30
import java.util.Objects;
31
import java.util.Optional;
32
import java.util.TreeMap;
33
import java.util.concurrent.TimeUnit;
34
import java.util.stream.Collectors;
35
import okhttp3.Call;
36
import okhttp3.Credentials;
37
import okhttp3.Headers;
38
import okhttp3.HttpUrl;
39
import okhttp3.MediaType;
40
import okhttp3.MultipartBody;
41
import okhttp3.OkHttpClient;
42
import okhttp3.Request;
43
import okhttp3.RequestBody;
44
import okhttp3.Response;
45
import okio.BufferedSink;
46
import okio.Okio;
47
import okio.Source;
48

49
public class BoxNetworkClient implements NetworkClient {
50

51
  private static final int BASE_TIMEOUT = 1;
52
  private static final double RANDOM_FACTOR = 0.5;
53
  private static final int DEFAULT_HTTP_PORT = 80;
54
  private static final int DEFAULT_HTTPS_PORT = 443;
55

56
  protected OkHttpClient httpClient;
57

58
  public BoxNetworkClient(OkHttpClient httpClient) {
×
59
    this.httpClient = httpClient;
×
60
  }
×
61

62
  public BoxNetworkClient() {
×
63
    httpClient = getDefaultOkHttpClientBuilder().build();
×
64
  }
×
65

66
  public OkHttpClient getHttpClient() {
67
    return httpClient;
×
68
  }
69

70
  public BoxNetworkClient withProxy(ProxyConfig config) {
71
    URI uri = URI.create(config.getUrl());
×
72
    String host = Objects.requireNonNull(uri.getHost(), "Invalid Proxy URL");
×
73

74
    String scheme =
×
75
        Optional.ofNullable(uri.getScheme())
×
76
            .filter(schema -> schema.startsWith("http"))
×
77
            .orElseThrow(() -> new IllegalArgumentException("Invalid Proxy URL: " + uri));
×
78

79
    int port =
×
80
        (uri.getPort() != -1)
×
81
            ? uri.getPort()
×
82
            : ("https".equalsIgnoreCase(scheme) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT);
×
83

84
    OkHttpClient.Builder clientBuilder =
×
85
        httpClient
86
            .newBuilder()
×
87
            .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port)));
×
88

89
    String username = config.getUsername();
×
90
    String password = config.getPassword();
×
91
    if (username != null && !username.trim().isEmpty() && password != null) {
×
92
      String basic = Credentials.basic(username, password, StandardCharsets.UTF_8);
×
93
      clientBuilder.proxyAuthenticator(
×
94
          (route, resp) ->
95
              resp.request().newBuilder().header("Proxy-Authorization", basic).build());
×
96
    }
97
    return new BoxNetworkClient(clientBuilder.build());
×
98
  }
99

100
  public FetchResponse fetch(FetchOptions options) {
101
    NetworkSession networkSession =
×
102
        options.getNetworkSession() == null ? new NetworkSession() : options.getNetworkSession();
×
103

104
    FetchOptions fetchOptions =
×
105
        networkSession.getInterceptors().stream()
×
106
            .reduce(
×
107
                options,
108
                (modifiedOptions, interceptor) -> interceptor.beforeRequest(modifiedOptions),
×
109
                (o1, o2) -> o2);
×
110

111
    boolean authenticationNeeded = false;
×
112
    Request request;
113
    FetchResponse fetchResponse = new FetchResponse.Builder(0, new TreeMap<>()).build();
×
114
    Exception exceptionThrown = null;
×
115

116
    int attemptNumber = 1;
×
117
    int numberOfRetriesOnException = 0;
×
118
    int attemptForRetry = 0;
×
119
    boolean shouldRetry = false;
×
120

121
    while (true) {
122
      request = prepareRequest(fetchOptions, authenticationNeeded, networkSession);
×
123

124
      Response response = null;
×
125
      String rawResponseBody = null;
×
126

127
      try {
128
        response = executeOnClient(request);
×
129

130
        Map<String, String> headersMap =
×
131
            response.headers().toMultimap().entrySet().stream()
×
132
                .collect(
×
133
                    Collectors.toMap(
×
134
                        Map.Entry::getKey,
135
                        e -> e.getValue().get(0),
×
136
                        (existing, replacement) -> existing,
×
137
                        () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
×
138

139
        String responseUrl =
×
140
            response.networkResponse() != null
×
141
                ? response.networkResponse().request().url().toString()
×
142
                : response.request().url().toString();
×
143

144
        attemptForRetry = attemptNumber;
×
145

146
        if (Objects.equals(
×
147
            fetchOptions.getResponseFormat().getEnumValue(), ResponseFormat.BINARY)) {
×
148
          fetchResponse =
×
149
              new FetchResponse.Builder(response.code(), headersMap)
×
150
                  .content(response.body().byteStream())
×
151
                  .url(responseUrl)
×
152
                  .build();
×
153
        } else {
154
          rawResponseBody = response.body() != null ? response.body().string() : null;
×
155
          fetchResponse =
×
156
              new FetchResponse.Builder(response.code(), headersMap)
×
157
                  .data(readJsonFromRawBody(rawResponseBody))
×
158
                  .url(responseUrl)
×
159
                  .build();
×
160
        }
161

162
        fetchResponse =
×
163
            networkSession.getInterceptors().stream()
×
164
                .reduce(
×
165
                    fetchResponse,
166
                    (modifiedResponse, interceptor) -> interceptor.afterRequest(modifiedResponse),
×
167
                    (o1, o2) -> o2);
×
168

169
      } catch (Exception e) {
×
170
        exceptionThrown = e;
×
171
        numberOfRetriesOnException++;
×
172
        attemptForRetry = numberOfRetriesOnException;
×
173
        if (response != null) {
×
174
          response.close();
×
175
        }
176
      }
×
177

178
      shouldRetry =
×
179
          networkSession
180
              .getRetryStrategy()
×
181
              .shouldRetry(fetchOptions, fetchResponse, attemptForRetry);
×
182

183
      if (shouldRetry) {
×
184
        double retryDelay =
×
185
            networkSession
186
                .getRetryStrategy()
×
187
                .retryAfter(fetchOptions, fetchResponse, attemptForRetry);
×
188
        if (retryDelay > 0) {
×
189
          try {
190
            TimeUnit.SECONDS.sleep((long) retryDelay);
×
191
          } catch (InterruptedException ie) {
×
192
            Thread.currentThread().interrupt();
×
193
            throw new BoxSDKError("Retry interrupted", ie);
×
194
          }
×
195
        }
196
        attemptNumber++;
×
197
        continue;
×
198
      }
199

200
      if (fetchResponse.getStatus() >= 300
×
201
          && fetchResponse.getStatus() < 400
×
202
          && fetchOptions.followRedirects) {
×
203
        if (!fetchResponse.getHeaders().containsKey("Location")) {
×
204
          throw new BoxSDKError(
×
205
              "Redirect response missing Location header for " + fetchOptions.getUrl());
×
206
        }
207
        URI originalUri = URI.create(fetchOptions.getUrl());
×
208
        URI redirectUri = URI.create(fetchResponse.getHeaders().get("Location"));
×
209
        boolean sameOrigin =
×
210
            originalUri.getHost().equals(redirectUri.getHost())
×
211
                && originalUri.getPort() == redirectUri.getPort()
×
212
                && originalUri.getScheme().equals(redirectUri.getScheme());
×
213
        return fetch(
×
214
            new FetchOptions.Builder(fetchResponse.getHeaders().get("Location"), "GET")
×
215
                .responseFormat(fetchOptions.getResponseFormat())
×
216
                .auth(sameOrigin ? fetchOptions.getAuth() : null)
×
217
                .networkSession(networkSession)
×
218
                .build());
×
219
      }
220

221
      if (fetchResponse.getStatus() >= 200 && fetchResponse.getStatus() < 400) {
×
222
        return fetchResponse;
×
223
      }
224

225
      throwOnUnsuccessfulResponse(
×
226
          request,
227
          fetchResponse,
228
          rawResponseBody,
229
          exceptionThrown,
230
          networkSession.getDataSanitizer());
×
231
    }
×
232
  }
233

234
  private static Request prepareRequest(
235
      FetchOptions options, boolean reauthenticate, NetworkSession networkSession) {
236
    Request.Builder requestBuilder = new Request.Builder().url(options.getUrl());
×
237
    Headers headers = prepareHeaders(options, reauthenticate, networkSession);
×
238
    HttpUrl url = prepareUrl(options);
×
239
    RequestBody body = prepareRequestBody(options);
×
240

241
    requestBuilder.headers(headers);
×
242
    requestBuilder.url(url);
×
243
    requestBuilder.method(options.getMethod().toUpperCase(Locale.ROOT), body);
×
244
    return requestBuilder.build();
×
245
  }
246

247
  private static Headers prepareHeaders(
248
      FetchOptions options, boolean reauthenticate, NetworkSession networkSession) {
249
    Headers.Builder headersBuilder = new Headers.Builder();
×
250

251
    networkSession.getAdditionalHeaders().forEach(headersBuilder::add);
×
252

253
    if (options.getHeaders() != null) {
×
254
      options.getHeaders().forEach(headersBuilder::add);
×
255
    }
256
    if (options.getAuth() != null) {
×
257
      if (reauthenticate) {
×
258
        options.getAuth().refreshToken(networkSession);
×
259
      }
260
      headersBuilder.add(
×
261
          "Authorization", options.getAuth().retrieveAuthorizationHeader(networkSession));
×
262
    }
263
    headersBuilder.add("User-Agent", USER_AGENT_HEADER);
×
264
    headersBuilder.add("X-Box-UA", X_BOX_UA_HEADER);
×
265
    return headersBuilder.build();
×
266
  }
267

268
  private static HttpUrl prepareUrl(FetchOptions options) {
269

270
    HttpUrl baseUrl = HttpUrl.parse(options.getUrl());
×
271
    if (baseUrl == null) {
×
272
      throw new IllegalArgumentException("Invalid URL " + options.getUrl());
×
273
    }
274
    HttpUrl.Builder urlBuilder = baseUrl.newBuilder();
×
275
    if (options.getParams() != null) {
×
276
      options.getParams().forEach(urlBuilder::addQueryParameter);
×
277
    }
278
    return urlBuilder.build();
×
279
  }
280

281
  private static RequestBody prepareRequestBody(FetchOptions options) {
282
    if (options.getMethod().equalsIgnoreCase("GET")) {
×
283
      return null;
×
284
    }
285
    String contentType = options.getContentType();
×
286
    MediaType mediaType = MediaType.parse(contentType);
×
287
    switch (contentType) {
×
288
      case "application/json":
289
      case "application/json-patch+json":
290
        return options.getData() != null
×
291
            ? RequestBody.create(sdToJson(options.getData()), mediaType)
×
292
            : RequestBody.create("", mediaType);
×
293
      case "application/x-www-form-urlencoded":
294
        return options.getData() != null
×
295
            ? RequestBody.create(sdToUrlParams(options.getData()), mediaType)
×
296
            : RequestBody.create("", mediaType);
×
297
      case "multipart/form-data":
298
        MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
×
299
        for (MultipartItem part : options.multipartData) {
×
300
          if (part.getData() != null) {
×
301
            bodyBuilder.addFormDataPart(part.getPartName(), sdToJson(part.getData()));
×
302
          } else {
303
            bodyBuilder.addFormDataPart(
×
304
                part.getPartName(),
×
305
                part.getFileName() != null ? part.getFileName() : "file",
×
306
                createMultipartRequestBody(part));
×
307
          }
308
        }
×
309
        return bodyBuilder.build();
×
310
      case "application/octet-stream":
311
        return RequestBody.create(readByteStream(options.getFileStream()), mediaType);
×
312
      default:
313
        throw new IllegalArgumentException("Unsupported content type " + contentType);
×
314
    }
315
  }
316

317
  protected Call createNewCall(Request request) {
318
    return this.httpClient.newCall(request);
×
319
  }
320

321
  private Response executeOnClient(Request request) throws IOException {
322
    return createNewCall(request).execute();
×
323
  }
324

325
  private static JsonNode readJsonFromRawBody(String rawResponseBody) {
326
    if (rawResponseBody == null) {
×
327
      return null;
×
328
    }
329

330
    try {
331
      return jsonToSerializedData(rawResponseBody);
×
332
    } catch (Exception e) {
×
333
      return null;
×
334
    }
335
  }
336

337
  private static void throwOnUnsuccessfulResponse(
338
      Request request,
339
      FetchResponse fetchResponse,
340
      String rawResponseBody,
341
      Exception exceptionThrown,
342
      DataSanitizer dataSanitizer) {
343
    if (fetchResponse.getStatus() == 0 && exceptionThrown != null) {
×
344
      throw new BoxSDKError(exceptionThrown.getMessage(), exceptionThrown);
×
345
    }
346
    try {
347
      throw BoxAPIError.fromAPICall(request, fetchResponse, rawResponseBody, dataSanitizer);
×
348
    } finally {
349
      try {
350
        if (fetchResponse.getContent() != null) {
×
351
          fetchResponse.getContent().close();
×
352
        }
353
      } catch (IOException ignored) {
×
354
      }
×
355
    }
356
  }
357

358
  private static int getRetryAfterTimeInSeconds(int attemptNumber, String retryAfterHeader) {
359

360
    if (retryAfterHeader != null) {
×
361
      return Integer.parseInt(retryAfterHeader);
×
362
    }
363

364
    double minWindow = 1 - RANDOM_FACTOR;
×
365
    double maxWindow = 1 + RANDOM_FACTOR;
×
366
    double jitter = (Math.random() * (maxWindow - minWindow)) + minWindow;
×
367
    return (int) (Math.pow(2, attemptNumber) * BASE_TIMEOUT * jitter);
×
368
  }
369

370
  public static RequestBody createMultipartRequestBody(MultipartItem part) {
371
    return new RequestBody() {
×
372
      @Override
373
      public MediaType contentType() {
374
        if (part.contentType != null) {
×
375
          return MediaType.parse(part.contentType);
×
376
        }
377
        return MediaType.parse("application/octet-stream");
×
378
      }
379

380
      @Override
381
      public void writeTo(BufferedSink sink) throws IOException {
382
        try (Source source = Okio.source(part.getFileStream())) {
×
383
          sink.writeAll(source);
×
384
        }
385
      }
×
386
    };
387
  }
388

389
  public static OkHttpClient.Builder getDefaultOkHttpClientBuilder() {
390
    return new OkHttpClient.Builder()
×
391
        .followSslRedirects(true)
×
392
        .followRedirects(false)
×
393
        .connectionSpecs(singletonList(MODERN_TLS))
×
394
        .retryOnConnectionFailure(false);
×
395
  }
396
}
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