Uploading files from Ionic 3 / Cordova to Minio / S3

Published: February 16, 2017  •  Updated: February 16, 2018  •  ionic3, cordova, spring, s3, javascript, java

In the previous post we played with Minio, a S3 compatible server, and wrote a Java application that uploads a file to the server. And in an earlier post we had a look at an example that sends pictures from an Ionic / Cordova application to a Spring Boot server.

In this blog we will do a combination of these two posts and create an example that sends pictures from an Ionic / Cordova application directly to a Minio server.

Minio developed a JavaScript client library that works with Minio and S3 servers. The primary usage of this library is for Node.js applications, but it also works in the browser. But that's not the way you should use this, because a client needs to know the keys to send requests to a S3 compatible server and storing these keys on the client is considered to be a security risk.

What an application should use instead are pre-signed URLs. This is not a Minio specific feature, Amazon S3 supports this as well. A pre-signed URL is like a one time usage key that allows a client to upload or fetch files from a S3 compatible server without having a security credentials with the service.

Flow of a connection with pre-signed URLs.

flow

The client sends a request (1) to a server that creates pre-signed URLs. This service can be implemented in any server side framework. For our example we will write this as a Spring Boot server. The service needs to know the security credentials for the Minio server (keys) to be able to create pre-signed URLs. The service should first check if the client has the proper permissions to upload or fetch files from the server. Then it makes a connection (2) to Minio to request a pre-signed URL and returns this URL back to the client. A pre-signed URL can be either used for uploading or for fetching files. In our example this will be an URL that allows the client to upload a file. The client can now directly connect (3) to the Minio server and either upload or fetch a file.

Client

First we create the client application. For that we copy the project from this post (GitHub).

Then we open the file src/pages/home/home.ts and make the necessary changes.

  private readFile(file: any) {
    const fileName = file.name;
    const reader = new FileReader();
    reader.onloadend = () => {
      const imgBlob = new Blob([reader.result], {type: file.type});
      this.fetchPresignUrl(fileName).subscribe(url => this.postData(url, imgBlob));
    };
    reader.readAsArrayBuffer(file);
  }

  fetchPresignUrl(fileName: string): Observable<string> {
    return this.http.get(`http://192.168.178.20:8080/getPreSignUrl?fileName=${fileName}`, 
                         {responseType: 'text'});
  }

  private postData(url: string, blob: Blob) {
    this.http.put(url, blob, {observe: 'response', responseType: 'text'})
      .pipe(catchError(e => this.handleError(e)),
        finalize(() => this.loading.dismiss())
      )
      .subscribe(resp => {
        this.showToast(resp.ok);
      });
  }

src/pages/home/home.ts

Before the app is able to upload the picture, it needs to fetch the pre-signed URL. For that the application calls the function fetchPresignUrl. This function sends a http get request to our Spring Boot server which is listening on the URL /getPreSignUrl and returns the pre-signed URL. After the app received the pre-signed URL it will initiate a PUT request to this URL and the binary data of the image in the body. This put request will directly connect to the Minio server and store the file on the server.

Server

We implement the server with Spring Boot and the starter-web dependency. In addition we need to add the Minio Java client library to the pom.xml.

<dependency>
  <groupId>io.minio</groupId>
  <artifactId>minio</artifactId>
  <version>4.0.2</version>
</dependency>

pom.xml

I created a configuration class for the Minio keys and endpoint address, that allows me to externalize these configuration options in the application.properties or application.yml file.

@ConfigurationProperties(prefix = "minio")
@Component
public class MinioConfig {

  private String endpoint;
  private String accessKey;
  private String secretKey;
  private String region;
  ....
}    

src/main/java/ch/rasc/upload/MinioConfig.java

Then the application instantiates a MinioClient and exposes it as a Spring managed bean.

@Bean
public MinioClient minioClient(MinioConfig config)
			throws InvalidEndpointException, InvalidPortException {
  return new MinioClient(config.getEndpoint(), config.getAccessKey(),
				config.getSecretKey());
}

src/main/java/ch/rasc/upload/Application.java

Next we create a RestController with one handler method that creates and returns pre-signed URLs. We inject the MinioClient bean and call the operation presignedPutObject to request a pre-signed urls from the Minio server.

@RestController
public class PreSignController {
  private final static String BUCKET_NAME = "uploads";
  private final MinioClient minioClient;

  public PreSignController(MinioClient minioClient) {
    this.minioClient = minioClient;
  }

  @PostConstruct
  public void createBucket() throws .... {		
    if (!minioClient.bucketExists(BUCKET_NAME)) {
      minioClient.makeBucket(BUCKET_NAME);
    }
  }

  @CrossOrigin
  @GetMapping("/getPreSignUrl")
  public String getPreSignUrl(@RequestParam("fileName") String fileName) throws ...{
    return this.minioClient.presignedPutObject(BUCKET_NAME, fileName, 60);
  }
}

src/main/java/ch/rasc/upload/PreSignController.java

In the @PostConstruct the application makes sure that the bucket with the name uploads exists. When the application requests a pre-signed URL, it must provide a bucket name, a file name and the expiration time of the URL in seconds. For this example the pre-sign URL will be valid for 60 seconds, after that the client is no longer able to use this URL.

To test the setup we have to start the Minio and our Spring Boot server (mvn spring-boot:run). Then we have to deploy the Ionic app to a real device or start it on an emulator. You should now be able to upload pictures from the app to the Minio server. If there is a problem check the security credentials and endpoint address for the Minio server in the application.yml file and make sure that the Spring Boot server and the Ionic app on the device/emulator can connect to the Minio server.

As mentioned before this will not only work with a Minio server you can use the same Spring Boot application with Amazon S3 as well. All you need to do is configuring the keys you get from Amazon and use the correct endpoint address (S3 Endpoints).

You find the entire source code for this project on GitHub.