Google Cloud Storage c Java: изображения и другие файлы в облаках

в 13:38, , рубрики: google app engine, Google Cloud Endpoints, google cloud storage, java, javascript, JSP, Веб-разработка, обработка изображений

В продолжение серии статей о веб-разработке на Java на платформе Google App Engine / Google Cloud Endpoints рассмотрим сервис для облачного хранения файлов Google Cloud Storage.

В целом схема выглядит следующим образом: сервер на бэкэнде генерирует временную ссылку (адрес) для передачи файла в определенный контейнер (bucket) нашего хранилища, которая на фронтэнде вставляется в форму для передачи файла. Пользователь на указанный адрес посылает POST HTTP-request с одним или несколькими файлами в теле запроса, файлы принимаются и размещаются в хранилище, и HTTP-request вместе с данными о размещенных файлах принимается сервлетом, который обработав информацию о размещенных файлах, возвращает пользователю HTTP response: JSON или text/html, или в общем что пожелаем.

Файлы сохраняются в хранилище, у сервлета есть в распоряжении ключ который дает возможность доступа к файлу, в частности можно выдать файл пользователю с помощью другого сервлета либо создать «статичную» ссылку (https://).
Доступ к хранилищу также доступен через веб-интерфейс, и из командной строки с помощью утилиты gsutil.

В качестве примера будем интегрировать Google Cloud Storage с приложением на GAE: hello-habrahabr-api.appspot.com + hello-habrahabr-webapp.appspot.com использовавшимся в предыдущих примерах.

Подключение Google Cloud Storage к проекту на Google App Engine / Google Cloud Endpoints

Для начала заходи в консоль разработчика ( App Engine Developer console):
appengine.google.com/dashboard?&app_id=hello-habrahabr-api (https://appengine.google.com/dashboard?&app_id={проект ID})
Переходим в меню Application Settings > Cloud Integration и внизу страницы нажимаем 'Create':
image
получаем сообщение «Cloud integration tasks have started»
Обратите внимание сейчас консоль разработчика Google существует в двух версия «старая» и «новая», функции постепенно переносятся из «старой» в «новую». Cloud Integration мы пока включаем из старой консоли разработчика (следует ожидать что эта функция скоро появиться и в новой консоли)
Перегружаем страницу, внизу в разделе Cloud Integration вместо кнопки 'Create' в видим сообщение «The project was created successfully. See the Basics section for more details.»
А немного выше в разделе Basics видим ссылку на подключенный Google Cloud Storage Bucket, по умолчанию ему присваивается такое же имя как у проекта GAE, в моем случае hello-habrahabr-api.appspot.com:
image
Кликаем по ссылке, она ведет нас на адрес console.developers.google.com/storage/browser{имя Bucket}/, в моем случае: console.developers.google.com/storage/browser/hello-habrahabr-api.appspot.com (естественно требует авторизации) и мы попадаем в Storage browser,
image
в котором мы можем создавать новые папки, загружать и удалять файлы, управлять правами доступа к файлам, в том числе мы можем сделать доступ к файлу публичным и получить постоянную ссылку на файл для веб (например если мы хотим использовать изображение или иной статический файл для веб-сайта), производить поиск и фильтрацию.
Cloud Storage предоставляет бесплатный bucket для каждого приложения на Google App Engine, но в этом случае веб-интерфейс Storage browser предоставляет только возможность просматривать содержимое bucket. Для того чтобы активировать все функции Storage browser и для создания дополнительных buckets надо включать биллинг и ввести данные своей кредитной карты (жмем на «Sign for free trial» и вводим данные кредитной карты, на 60 дней получаем бесплатный, вернее в пределах $300, пробный период)

Создание временной ссылки для загрузки файла

Необходимые импорты:

import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.blobstore.UploadOptions;

команда для создания ссылки:

String uploadUrl = BlobstoreServiceFactory.getBlobstoreService().createUploadUrl(
    "/upload", // path to upload handler (servlet)
    UploadOptions.Builder.withGoogleStorageBucketName("hello-habrahabr-api.appspot.com") // bucket name
)

Например, если мы создаем API на Cloud Endpoints, то API, возвращающий ссылку для загрузки файла будет выглядеть:

package com.appspot.hello_habrahabr_api;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.blobstore.UploadOptions;

import java.io.Serializable;

@Api(name = "uploadAPI",
        version = "ver.1.0",
        scopes = {Constants.EMAIL_SCOPE},
        clientIds = {
                Constants.WEB_CLIENT_ID,
                Constants.API_EXPLORER_CLIENT_ID
        },
        description = "uploads API")

public class UploadAPI {
    // add this class to <init-param> of <servlet-name>SystemServiceServlet</servlet-name> in web.xml

    /* API methods can return JavaBean Objects only, so we use this as a wrapper for String */
    class StringWrapperObject implements Serializable {
        private String string;

        public StringWrapperObject() {
        }

        public StringWrapperObject(String string) {
            this.string = string;
        }

        public String getString() {
            return string;
        }

        public void setString(String string) {
            this.string = string;
        }
    } // end of StringWrapperObject class

    @ApiMethod(
            name = "getCsUploadURL",
            path = "getCsUploadURL",
            httpMethod = ApiMethod.HttpMethod.POST
    )
    @SuppressWarnings("unused")
    public StringWrapperObject getCsUploadURL() {

        String uploadURL = BlobstoreServiceFactory.getBlobstoreService().createUploadUrl(
                "/cs-upload", // upload handler servlet address
                UploadOptions.Builder.withGoogleStorageBucketName(
                        "hello-habrahabr-api.appspot.com" // Cloud Storage bucket name
                )
        );

        return new StringWrapperObject(uploadURL);
    }

}

Форма на фронтэнде:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
    <title>File Upload Form</title>
</head>

<body>
<hr>
One File:
<hr>
<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="Upload">
</form>
<hr>
Multiple Files:
<hr>
<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple>
    <input type="submit" value="Upload">
</form>

<!-- JavaScript -->
<script>
    'use strict';

    $(document).ready(function () {

        var url = "https://hello-habrahabr-api.appspot.com/_ah/api/uploadAPI/ver.1.0/getCsUploadURL";

        $.ajax(url, {
            method: "POST", // (default: 'GET')
            async: false, // default: true
            processData: true, // (default: true) (By default, data passed in to the 'data' option as an object)
            success: function (data) {

                console.log("responce received:");
                console.log(data);

                $("form").attr("action", data.string);

            }
        })
    })
</script>
</body>

</html>

Та же форма в виде JSP:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%@ page import="com.google.appengine.api.blobstore.BlobstoreService" %>
<%@ page import="com.google.appengine.api.blobstore.BlobstoreServiceFactory" %>
<%@ page import="com.google.appengine.api.blobstore.UploadOptions" %>

<%
    BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
%>
<html>
<head>
    <title>File Upload Form</title>
</head>
<body>
<hr>
One File:
<hr>
<form
        action="<%= blobstoreService.createUploadUrl("/cs-upload", UploadOptions.Builder.withGoogleStorageBucketName("hello-habrahabr-api.appspot.com")) %>"
        method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="Upload">
</form>
<hr>
Multiple Files:
<hr>
<form
        action="<%= blobstoreService.createUploadUrl("/cs-upload", UploadOptions.Builder.withGoogleStorageBucketName("hello-habrahabr-api.appspot.com")) %>"
        method="post" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple>
    <input type="submit" value="Upload">
</form>


</body>
</html>

Выдаваемая ссылка будет выглядеть примерно так:

https://hello-habrahabr-api.appspot.com/_ah/upload/AMmfu6YJ0ci-sKP5k98sKaJEUjYwBFbkVfQ7iylXTJV52_gy5HIECKNG52IPUCJ9PB3wpL2wxgX82GkGkzetHt-6fuu4yzAzFFhD8HGOcD7eJ48KJLnKnb2EqbuoFEdyuc8r_FTR7779IIaf42rf_jhkl7Hju3GxWDmxh2WtmcPR2AbB9OWlQhYxBIWtZgBW9OsHO50pI21/ALBNUaYAAAAAVp2DRSZYST46t2kPmrGrrBoY3AFjyOiD/

Но HTTP-response будет создаваться сервлетом находящимся у нас по адресу /cs-upload

Сервлет формирующий HTTP-response (upload handler)

Этот сервлет будет выглядеть следующим образом:

package com.appspot.hello_habrahabr_api;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.blobstore.FileInfo;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.ServingUrlOptions;
import com.google.appengine.repackaged.com.google.gson.Gson;
import com.google.appengine.repackaged.com.google.gson.GsonBuilder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.logging.Logger;

public class CSUploadHandlerServlet extends HttpServlet {

    private final static Logger LOG = Logger.getLogger(CSUploadHandlerServlet.class.getName());
    private final static String HOST = "https://hello-habrahabr-api.appspot.com";

    /* Object to be returned as JSON in HTTP-response (and can be stored in data base) */
    class UploadedFileData {
        FileInfo fileInfo;
        String BlobKey;
        String fileServeServletLink;
        String servingUrlFromgsObjectName;
        String servingUrlFromGsBlobKey;
    } // end of uploadedFileData

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

        // Returns the FileInfo for any files that were uploaded, keyed by the upload form "name" field.
        // This method should only be called from within a request served by the destination of a createUploadUrl call.
        // https://cloud.google.com/appengine/docs/java/javadoc/com/google/appengine/api/blobstore/BlobstoreService#getFileInfos-HttpServletRequest-
        java.util.Map<java.lang.String, java.util.List<FileInfo>> fileInfoListsMap = BlobstoreServiceFactory.getBlobstoreService().getFileInfos(req);
        LOG.warning("[LOGGER]: " + new Gson().toJson(fileInfoListsMap));

        ArrayList<UploadedFileData> uploadedFilesDataList = new ArrayList<>();

        for (java.util.List<FileInfo> fileInfoList : fileInfoListsMap.values()) {

            for (FileInfo fileInfo : fileInfoList) {

                UploadedFileData uploadedFileData = new UploadedFileData();
                uploadedFileData.fileInfo = fileInfo;

                LOG.warning("uploadedFileData created:" + new Gson().toJson(uploadedFileData));
                BlobKey blobKey = BlobstoreServiceFactory.getBlobstoreService().createGsBlobKey(fileInfo.getGsObjectName());
                uploadedFileData.BlobKey = blobKey.getKeyString();
                uploadedFileData.fileServeServletLink = HOST + "/serve?blob-key=" + blobKey.getKeyString();

                // Use Images Java API to create serving URL
                // works only for images (PNG, JPEG, GIF, TIFF, BMP, ICO, WEBP)
                for (com.google.appengine.api.images.Image.Format type : com.google.appengine.api.images.Image.Format.values()) {
                    LOG.warning("com.google.appengine.api.images.Image.Format type: " + type.toString());
                    LOG.warning("fileInfo.getContentType(): " + fileInfo.getContentType());
                    if (fileInfo.getContentType().toLowerCase().contains(type.toString().toLowerCase())) {
                        uploadedFileData.servingUrlFromgsObjectName = ImagesServiceFactory.getImagesService().getServingUrl(ServingUrlOptions.Builder.withGoogleStorageFileName(fileInfo.getGsObjectName())); // should be the same as servingUrlFromGsBlobKey
                        uploadedFileData.servingUrlFromGsBlobKey = ImagesServiceFactory.getImagesService().getServingUrl(ServingUrlOptions.Builder.withBlobKey(blobKey)); // should be the same as servingUrlFromgsObjectName

                    }
                }

                uploadedFilesDataList.add(uploadedFileData);

            }

        }

        res.setContentType("application/json");
        res.setCharacterEncoding("UTF-8");
        PrintWriter pw = res.getWriter(); //get the stream to write the data
        Gson gson = new GsonBuilder().disableHtmlEscaping().create();
        pw.println(
                gson.toJson(uploadedFilesDataList)
        );
        LOG.warning("uploadedFilesDataMap" + new Gson().toJson(uploadedFilesDataList));
        pw.close(); //closing the stream

    } // doPost End

}

и будет выдавать в HTTP-response JSON такого вида:

JSON

[{
    "fileInfo": {
        "contentType": "image/svg+xml",
        "creation": "Jan 18, 2016 7:16:18 PM",
        "filename": "Sun_symbol.svg",
        "size": 188,
        "md5Hash": "YWZmM2UzMzk2ZDk2NTc0ZWM3NDI0YjYyMDMxZGIxYTM=",
        "gsObjectName": "/gs/hello-habrahabr-api.appspot.com/L2FwcGhvc3RpbmdfcHJvZC9ibG9icy9BRW5CMlVxM3RWVU9nMWVxdmxfQ1dlSk5HaXIwNHpwamE3Y2FhVzlwRjF3dk4zQnFlU3JBMVkxaERGT2NRbXpLZ0pBOW0yTjZiaXhsZjFGWElmdFRNX1p2WXF4WTJnTlF1Zy42LWZnTzcybk1xNG03X1pw"
    },
    "BlobKey": "AMIfv968eMcYEHQml68MAl4NVtOQGKjXUWadeyP7njbaVhHXq_1xDAnRQgHeHrOv4RPLm-KdmqEHP5nb1zNuCFFszRxOVUV4Z97B9slNi7SSGWZ1qKbYcbJi2nl5Z7JX9g1xN4RclYpmLPLfh5k2jAULi6p9g84JSyh5uP3RDkNnPuXkBjxSuBTOWCVxOmpRS-xsB1YedYAgF6cRYLq0hVpm_bOY3Cbl3Ai0W-_req9jxcuPWkoguhHiZ2SSBRF9NlvgG_hCf3vouYtYS2O9DBbioeOL_p1Ck8gfvhQiiK6XpXM4S7vAYqYZCQKJ_9T4tswy075-e6NlsdtXGj9zhSxCy_GfSSBrnvbwcQUDA7lN_IYIfm0QWs-XgzBl9izizUeE46jOI-1O",
    "fileServeServletLink": "https://hello-habrahabr-api.appspot.com/serve?blob-key=AMIfv968eMcYEHQml68MAl4NVtOQGKjXUWadeyP7njbaVhHXq_1xDAnRQgHeHrOv4RPLm-KdmqEHP5nb1zNuCFFszRxOVUV4Z97B9slNi7SSGWZ1qKbYcbJi2nl5Z7JX9g1xN4RclYpmLPLfh5k2jAULi6p9g84JSyh5uP3RDkNnPuXkBjxSuBTOWCVxOmpRS-xsB1YedYAgF6cRYLq0hVpm_bOY3Cbl3Ai0W-_req9jxcuPWkoguhHiZ2SSBRF9NlvgG_hCf3vouYtYS2O9DBbioeOL_p1Ck8gfvhQiiK6XpXM4S7vAYqYZCQKJ_9T4tswy075-e6NlsdtXGj9zhSxCy_GfSSBrnvbwcQUDA7lN_IYIfm0QWs-XgzBl9izizUeE46jOI-1O"
}, {
    "fileInfo": {
        "contentType": "image/jpeg",
        "creation": "Jan 18, 2016 7:16:18 PM",
        "filename": "world_map_04.jpg",
        "size": 44680,
        "md5Hash": "MzQyMzliZGQ4NmYyNmZiNzc3ZjAyMzBhNmM4NDVmNWE=",
        "gsObjectName": "/gs/hello-habrahabr-api.appspot.com/L2FwcGhvc3RpbmdfcHJvZC9ibG9icy9BRW5CMlVxM3RWVU9nMWVxdmxfQ1dlSk5HaXIwNHpwamE3Y2FhVzlwRjF3dk4zQnFlU3JBMVkxaERGT2NRbXpLZ0pBOW0yTjZiaXhsZjFGWElmdFRNX1p2WXF4WTJnTlF1Zy5Ld1pSRTQ2M3J3ZWYxa3Bm"
    },
    "BlobKey": "AMIfv95nBw0rYnC39nCATxvyecFw0JEe64eTm-OhpsSsrR3Idv_rPbO2c6xTDx3q1xkulXfUyapqtEXdeQQur7FcppXa9rRcnlF7QnU8jur7a7AP3T5Ze_-bdD_F6F5mGP9Tteo7p7cN4UccqoYhnAyabAIsJBq3pZIwX2NlHhqcK_aelnu1tl3aszZU4cVmhLiZGE8hFvgDQyt-2oB4DurXUKTwGC56cZykCdYONO0EDETgkImiytbtk1iV_muyYZzfd7on3OS0LSmY8ls7QIcm1IMgl5jDPJANlsk_iWtnRJfEiYAC9pZ7DfhSPxTeYzko0b1TXrKuGjpG8cYMcxiA0Cmeya8y-7SCQuWQLlKCX8WFpIVOr26UguDaq8SFYplALbxgQUiB",
    "fileServeServletLink": "https://hello-habrahabr-api.appspot.com/serve?blob-key=AMIfv95nBw0rYnC39nCATxvyecFw0JEe64eTm-OhpsSsrR3Idv_rPbO2c6xTDx3q1xkulXfUyapqtEXdeQQur7FcppXa9rRcnlF7QnU8jur7a7AP3T5Ze_-bdD_F6F5mGP9Tteo7p7cN4UccqoYhnAyabAIsJBq3pZIwX2NlHhqcK_aelnu1tl3aszZU4cVmhLiZGE8hFvgDQyt-2oB4DurXUKTwGC56cZykCdYONO0EDETgkImiytbtk1iV_muyYZzfd7on3OS0LSmY8ls7QIcm1IMgl5jDPJANlsk_iWtnRJfEiYAC9pZ7DfhSPxTeYzko0b1TXrKuGjpG8cYMcxiA0Cmeya8y-7SCQuWQLlKCX8WFpIVOr26UguDaq8SFYplALbxgQUiB",
    "servingUrlFromgsObjectName": "http://lh3.googleusercontent.com/biRXwDZgclmYJa4hDUwOqBMK--VDNwj-9kZ27vzachWAGBunKVDelImXC9S5EZIhDm1T4xbyq8djFqNKkTzkSpcVkgbPO2ovxg",
    "servingUrlFromGsBlobKey": "http://lh3.googleusercontent.com/biRXwDZgclmYJa4hDUwOqBMK--VDNwj-9kZ27vzachWAGBunKVDelImXC9S5EZIhDm1T4xbyq8djFqNKkTzkSpcVkgbPO2ovxg"
}]

То есть загруженный файл мы можем потом отдавать пользователю используя либо ссылку вида
http://lh3.googleusercontent.com/biRXwDZgclmYJa4hDUwOqBMK--VDNwj-9kZ27vzachWAGBunKVDelImXC9S5EZIhDm1T4xbyq8djFqNKkTzkSpcVkgbPO2ovxg
— если это файл изображения, либо (в любом случае) сервлет ссылка на который будет выглядеть как
https://hello-habrahabr-api.appspot.com/serve?blob-key=AMIfv95nBw0rYnC39nCATxvyecFw0JEe64eTm-OhpsSsrR3Idv_rPbO2c6xTDx3q1xkulXfUyapqtEXdeQQur7FcppXa9rRcnlF7QnU8jur7a7AP3T5Ze_-bdD_F6F5mGP9Tteo7p7cN4UccqoYhnAyabAIsJBq3pZIwX2NlHhqcK_aelnu1tl3aszZU4cVmhLiZGE8hFvgDQyt-2oB4DurXUKTwGC56cZykCdYONO0EDETgkImiytbtk1iV_muyYZzfd7on3OS0LSmY8ls7QIcm1IMgl5jDPJANlsk_iWtnRJfEiYAC9pZ7DfhSPxTeYzko0b1TXrKuGjpG8cYMcxiA0Cmeya8y-7SCQuWQLlKCX8WFpIVOr26UguDaq8SFYplALbxgQUiB
, где
serve
— путь к сервлету,
blob-key
— параметр с помощью которого мы сможем найти требуемый файл, в наиболее очевидном варианте его значением будет BlobKey.
Следует отметить что BlobKey не дает прямого доступа к файлу в обход сервлета, а сервлет может передавать или не передавать файл в зависимости от установленных нами критериев, в т.ч. мы можем использовать предоставляемою Google App Engine аутентификацию OAuth2.0, использовать дополнительные параметры в запросе и т.д.
Сервлет отдающий файл может выглядеть следующим образом:

package com.appspot.hello_habrahabr_api;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.logging.Logger;

public class FileServeServlet extends HttpServlet {

    private final static Logger LOG = Logger.getLogger(CSUploadHandlerServlet.class.getName());

    private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();

    // works both for Bloobstore and Cloud Storage
    public void doGet(
            HttpServletRequest req,
            HttpServletResponse res
    )
            throws IOException {

        // --- check user:
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();
        if (user == null) {
            LOG.warning("[LOGGER] User not logged in");

        } else {
            LOG.warning("[LOGGER] user: " + user.getEmail());
        }

        // get parameter from url constructed with:
        // "/serve?blob-key=" + blobKey.getKeyString()
        BlobKey blobKey = new BlobKey(req.getParameter("blob-key"));
        
        blobstoreService.serve(blobKey, res);
    }
}

Images Java API

Как уже было показано выше, используя Images Java API мы можем с помощью

ImagesServiceFactory.getImagesService().getServingUrl(
        ServingUrlOptions.Builder.withGoogleStorageFileName(fileInfo.getGsObjectName())
); 

или с помощью

ImagesServiceFactory.getImagesService().getServingUrl(
        ServingUrlOptions.Builder.withBlobKey(blobKey)
); 

получить URL предоставляющий файл изображения. Такой метод загрузки файла работает быстрее чем с помощью сервлета, но соответственно мы имеем как бы ссылку на «статичный» файл и не можем обрабатывать запрос как в случае использования сервлета.
Но такая созданная ссылка на файл может быть, так же как создана, удалена с помощью метода .deleteServingUrl(BlobKey blobKey) Сам файл при этом из хранилища не удаляется, и на него может быть создана новая ссылка. Т.е. мы можем делать такие ссылки «одноразовыми» создавая и удаляя их в случае необходимости.

Кроме того к ссылкам на изображения созданным с помощью getServingUr() можно добавлять параметры изменяющие изображение, в формате
http://[image-url]=s200-fh-p-b10-c0xFFFF0000
:

s640 — генерирует изображение размером в 640 пикселей на самой большой грани
s0 — оригинальный размер изображения (по умолчанию выдаваемое изображение уменьшается!)
w100 — генерирует изображение шириной 100 пикселей
h100 — генерирует изображение высотой 100 пикселей
c — обрезает изображение до заданных размеров (s200, например)
p — «умное» обрезание изображения, старается обрезать до лица (работает не очень успешно)
pp — альтернативный метод сделать то же что в предыдущем пункте (работает аналогично)
cc — генерирует круглое изображение
fv — переворачивает вертикально
fh — переворачивает горизонтально
r{90} — поворачивает на указанное число градусов по часовой стрелке
rj — выдает изображение в формате JPG
rp — выдает изображение в формате PNG
rw — выдает изображение в формате WebP
rg — выдает изображение в формате GIF
b10 — добавляет рамку указанной ширины (в данном случае 10px)
c0xffff0000 — устанавливает цвет рамки (в данном случае красный)
d — добавляет header запускающий загрузку в браузере
h — выводит HTML страницу содержащую изображение

Например, из исходного изображения:
image
с параметрами:
=w100-h100-cc
— можно сгенерировать круглый аватар;
image
с параметрами:
=s200-b3-c0xffff0000
— thumbnail размером максимальной грани в 200px с красной рамкой шириной 3px:
image
В отличии от использования CSS, в данном случае, с сервера будет загружаться изображение уже уменьшенное до нужных размеров.

Больше параметров, см. на stackoverflow.com/questions/25148567/list-of-all-the-app-engine-images-service-get-serving-url-uri-options

Доступ к хранилищу из командной строки (утилита gsutil)

gsutil написана на Python (требует Python 2.6.x или 2.7.x) и работает из командной строки on Linux/Unix, Mac OS, и Windows (XP и выше).
Инструкции по инсталляции: cloud.google.com/storage/docs/gsutil_install
После инсталляции запускаем:

gcloud auth login

и авторизуемся (аналогично изложенному на habrahabr.ru/post/268863 )
gsutil представляет доступ к контейнерам хранилища с использованием команд похожих на привычны команды консоли Linux/Unix, файлы в хранилище обозначаются «путем» вида gs://{имя контейнера}, например gs://hello-habrahabr-api.appspot.com
Так чтобы вывести информацию о файлах в контейнере вводим команду

gsutil ls gs://hello-habrahabr-api.appspot.com 

для всех файлов во всех контейнерах доступных текущему пользователю (Google account):

gsutil ls gs://*

для вывода командой ls более подробной информации указываем параметр -l, для полной информации о файлах указываем параметр -L:
Google Cloud Storage c Java: изображения и другие файлы в облаках - 7

Соответственно можно использовать команды cp, mv, rm в качестве адресов файлов в контейнере используя
gs://{имя контейнера} /{имя файла в контейнере}
и обычные пути для файлов на локальной ОС, также поддерживаются wildcard characters (
gs://*
) Подробнее о командах gsutil: cloud.google.com/storage/docs/gsutil
Таким образом, используя возможности gsutil можно организовывать и автоматизировать работу с файлами в хранилище.

Ссылки:

Автор: ageyev

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js