Clients and Connections

A FilesClient holds everything its requests need: the API root of your site, credentials, a workspace, time limits, retries and the connections its requests use. Build one with FilesClient.builder(), reach each resource's operations from it, such as client.users(), and close it when you are done.

Its settings never change once it is built, so several clients, for different users, sites or workspaces, can work at the same time. What a client returns keeps using that client and the request options it was requested with: saving a returned User, or loading the next page of a listing, sends the same credentials to the same site, whatever you change afterward. A client is safe to use from many threads.

Example Request

import com.files.FilesClient;
import com.files.ListIterator;
import com.files.exceptions.*;
import com.files.exceptions.ApiErrorException.*;
import com.files.models.User;

try (FilesClient client = FilesClient.builder()
    .apiKey("YOUR_API_KEY")
    .apiRoot("https://MY-SUBDOMAIN.files.com")
    .workspaceId(0L)
    .build()) {
  try {
    ListIterator<User> users = client.users().list();
    for (User user : users.listAutoPaging()) {
      user.setNotes("Reviewed");
      user.save();
    }
  } catch (NotAuthenticatedException e) {
    System.out.println("Authentication Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
  } catch (SdkException e) {
    System.out.println("Unknown Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
  }
}

The Static Methods

The static methods, such as User.list(), use FilesClient.defaultClient(): a client with the static settings FilesClient.apiKey, FilesClient.session, FilesClient.workspaceId, FilesClient.language and the FilesClient.setProperty properties as they are when each call is made. Objects and listings the static methods return keep the settings they were requested with, so changing FilesClient.apiKey later affects only later calls. The default client reads maxNetworkRetries (or upstreamMaxAttempts), initialRetryDelayMillis and maximumRetrySeconds again before each attempt of a request, so a change also reaches a request already retrying; uploads and downloads keep the values they started with.

An object you construct yourself, such as new User(attributes), uses the default client until you give it another with setClient.

Example Request

import com.files.FilesClient;
import com.files.models.User;
import java.util.HashMap;

FilesClient.apiKey = "YOUR_API_KEY";
User user = User.find(userId, null);

HashMap<String, Object> attributes = new HashMap<>();
attributes.put("username", "new-user");
User created = new User(attributes);
created.setClient(client);
created.save();

Request Options

Each operation also takes a map of request options for that one call:

  • api_key or session_id authenticates with other credentials.
  • workspace_id acts in another workspace; null sends none.
  • api_root sends the call to another site, such as https://OTHER-SUBDOMAIN.files.com; the client's own API root does not change.
  • cancellation is a CancellationToken that stops the call.
  • timeout is a java.time.Duration: the time limit of the call, across all its attempts.
  • max_retries is an Integer that replaces the client's retry limit.

The maps you pass are copied, so changing them afterward changes nothing. Objects a call returns keep its api_key, session_id, workspace_id and api_root; a listing also keeps the other options for the pages it requests.

Example Request

import com.files.models.User;
import java.time.Duration;
import java.util.HashMap;

HashMap<String, Object> options = new HashMap<>();
options.put("workspace_id", 12L);
options.put("timeout", Duration.ofSeconds(20));

User user = client.users().find(userId, null, options);

Retries

A request that fails in a way that may be temporary is sent again, up to maxRetries times (3 by default, and 0 to send each request once): after a connection or read failure, HTTP 429, HTTP 502 or above, or HTTP 500 in answer to a GET. HTTP 501 is never retried, and neither is HTTP 500 in answer to another method. The first retry waits retryDelay (100 ms), and each later one twice as long as the one before, up to maxRetryDelay (10 seconds). After HTTP 429 or 503, a longer Retry-After within that maximum is waited instead.

A request is not retried when it cannot be sent as written, such as a URL without a host or with a scheme other than HTTP or HTTPS, when the server's certificate or name does not verify, or when its redirects loop or go past the limit. Other TLS and protocol failures are retried. A retried request that changes data may take effect twice when an earlier attempt reached Files.com but its answer was lost.

A retry is logged at WARN with its number, its wait and why it was needed, such as Retrying the GET request (retry 1 of 3) in 100 ms after HTTP status 503. A failure without a response is described by its kind, such as the connection could not be made (ConnectException), in logs and in the exception's message; the message of the underlying exception, which can repeat URLs or other details, is logged only at DEBUG. The exception's message then names the request by its method and path, without its query or host, such as (response to GET /api/rest/v1/users/1), as does the message of an error response without a structured error.

Example Request

import com.files.FilesClient;
import java.time.Duration;

FilesClient client = FilesClient.builder()
    .apiKey("YOUR_API_KEY")
    .maxRetries(5)
    .retryDelay(Duration.ofMillis(250))
    .maxRetryDelay(Duration.ofSeconds(30))
    .build();

Site Regions

Some sites must be reached through a server of their own. When Files.com answers a request with a not-authenticated/lockout-region-mismatch error naming that server, the client sends the request there instead, as one of its retries, and sends the rest of that call's requests there, such as those of an upload. When the request used the client's own credentials, the client keeps sending its later requests there too; a call with its own api_key or session_id, or one that creates a session, moves only itself, and the next such call starts at the client's server again, unless it continues an upload, with the same options, from the part that call returned, as FileUploadPart.putInputStream does. By default it does so only for Files.com's own servers, whose names end in files.com, over HTTPS; the credentials never go anywhere else. RegionRelocationPolicy.DISABLED reports the error instead, as LockoutRegionMismatchException, and a policy of your own can approve your custom domain. For the static methods, set the regionRelocation property to false to disable it.

Example Request

import com.files.FilesClient;
import com.files.RegionRelocationPolicy;
import java.net.URI;

FilesClient client = FilesClient.builder()
    .apiKey("YOUR_API_KEY")
    .regionRelocation((apiRoot, host) -> "files.example.com".equalsIgnoreCase(host)
        ? URI.create("https://files.example.com")
        : RegionRelocationPolicy.FILES_COM_HOSTS.relocate(apiRoot, host))
    .build();

Time Limits and Cancellation

A client waits up to connectTimeout (30 seconds) to connect, readTimeout (80 seconds) for the next bytes of a response, both while waiting for its headers and while reading its body, and connectionAcquisitionTimeout (10 seconds) for a connection from its pool. callTimeout, or the timeout request option, limits a whole call, its retries and waits included.

A call stops when its CancellationToken is cancelled, its time limit passes, the thread making it is interrupted, or its client is closed. Its request is aborted, even while it waits for the server or reads a response, and the call throws OperationCancelledException, whose getReason() is CANCELLED, TIMED_OUT or INTERRUPTED. An interrupted thread keeps its interrupt status, and a call made on a thread that is already interrupted stops at once.

One exception: a read of your own stream, such as an attachment's, cannot be interrupted in Java 8. The call stops as soon as that read returns.

Example Request

import com.files.CancellationToken;
import com.files.exceptions.OperationCancelledException;
import java.util.HashMap;

CancellationToken token = new CancellationToken();
HashMap<String, Object> options = new HashMap<>();
options.put("cancellation", token);

// On another thread, when the work is no longer needed:
token.cancel();

try {
  client.users().list(null, options).all();
} catch (OperationCancelledException e) {
  System.out.println("Stopped: " + e.getReason());
}

Connections

Each client has an HttpTransport of its own, an Apache HttpClient with its connection pool, unless you give it one to share. Give a client HttpTransport.builder() settings to configure its own transport, which it closes when it is closed. Build a transport yourself and give it to several clients to share its connections: closing a client leaves it open, and you close it once no client uses it.

customize adjusts the Apache HttpClientBuilder, for a proxy, credentials for it, interceptors for instrumentation or default headers. sslContext sets trusted certificates or a client certificate; connectionManager uses a pool you manage. Whatever you customize, the SDK turns off Apache's own retries, so each request stays within the client's retry limit, and it removes Files.com credentials from every request, first or redirected, that is not sent to the API root its call started at. It does so as each request is written to its connection, so replacing the HTTP processor, request executor or redirect strategy does not change it.

Closing a client cancels its calls and transfers still running and waits for them to stop, even if the closing thread is interrupted, which then keeps its interrupt status; a second close waits the same way. A transfer blocked reading your own stream stops once that read returns. A close from within a call or transfer, such as in a progress listener, cancels the same way but returns at once, since the work it would wait for may be waiting for it. Afterward the client, and what it returned, throw IllegalStateException for new requests.

Example Request

import com.files.FilesClient;
import com.files.net.HttpTransport;
import org.apache.http.HttpHost;

try (HttpTransport shared = HttpTransport.builder()
    .maxConnections(100)
    .customize(http -> http.setProxy(new HttpHost("proxy.example.com", 8080)))
    .build()) {
  FilesClient first = FilesClient.builder().apiKey("FIRST_KEY").transport(shared).build();
  FilesClient second = FilesClient.builder().apiKey("SECOND_KEY").transport(shared).build();
  // ...
  first.close();
  second.close();
}