File storage backend
CiviForm currently supports file uploads using AWS S3. There is an effort in progress to add support for Azure Blob Storage. Support for other storage services like Google Cloud Storage is planned. This document will provide an overview of the classes and interfaces that are used to implement support for new storage providers.
File upload functionality uses the Azurite / Localstack emulators. For Azure-based file uploads to work in your browser, you will need to add the following line to your local
/etc/hosts
file:127.0.0.1 azurite
127.0.0.1 localhost.localstack.cloud
# Required for test AWS S3 bucket
127.0.0.1 civiform-local-s3.localhost.localstack.cloud
This is because the application makes requests to Azurite using its container name, which can only be resolved when the request is made from within the Docker network. These container names to be mapped to the loopback IP address in order for the browser to resolve requests to
azurite
to upload and get uploaded images.Note to Googlers: if you are using your Chrome profile for your Corp account, you'll run into CORS and UberProxy errors. Switch to a personal or incognito Chrome profile.
The
StorageClient
interface is used to decouple classes for interacting with specific storage providers from the rest of the codebase. New controllers and views should depend on the StorageClient interface rather than one of its implementations. In order to determine which StorageClient
implementation to use at runtime, we use Guice for dependency injection. The CloudStorageModule
Guice module reads in the cloud.storage
property set in application.conf
, and binds the corresponding implementation to the implementation. For more info on how this works, see the Guice documentation on bindings.Each implementation of StorageClient uses an implementation of an inner
Client
interface depending on the environment the application is running in (dev, test, or prod). The inner Client
implementations for new classes implementing StorageClient
should have one client that interacts with the emulator, one stub (for unit tests), and one client that interacts with a real instance of the storage backend.
Class Diagram for StorageClient interface
Implementations of the
StorageUploadRequest
class hold all the information necessary to upload a file from the browser. Implementations of StorageClient
are used to generate instances of StorageUploadRequest
implementations (BlobStorageUploadRequest
,SignedS3UploadRequest
) and the generated instances are passed to the render methods in view classes.
Class diagram for StorageUploadRequest interface
The controller and view classes that interact with the interfaces above might need to change the behavior of a method (for example, render a different template) depending on which storage provider is being used. To accomplish this, we make use of the Strategy Pattern. Each class where the strategy pattern is being used has its own corresponding strategy interface. Implementations are bound to the interface in the
CloudStorageModule
file. Below is an example of how the strategy pattern is used:
Strategy pattern example
Last modified 13d ago