Google Cloud Storage c Java: зображення та інші файли в хмарах

В продовження серії статей про веб-розробки на 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 static final Logger LOG = Logger.getLogger(CSUploadHandlerServlet.class.getName());
private static final String HOST = "https://hello-habrahabr-api.appspot.com";

/* Object to be returned as in JSON 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 URL serving
// 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

} // End doPost

}

і буде видавати 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 static final 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
:


Відповідно можна використовувати команди
cp
,
mv
,
rm
в якості адрес файлів в контейнері використовуючи

gs://{ім'я контейнера} /{ім'я файлу в контейнері}
і звичайні шляхи для файлів з локальної ОС, також підтримуються wildcard characters (

gs://*
) Детальніше про командах gsutil: cloud.google.com/storage/docs/gsutil
Таким чином, використовуючи можливості gsutil можна організовувати і автоматизувати роботу з файлами у сховище.

Посилання:



Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.