Использование blobstore с конечной точкой Google и Android
Я разрабатываю проект Android с Android-движком, используя плагин eclipse. Один из аспектов приложения - позволить пользователю Alpha отправлять фотографии пользователю Bravo. Для этого у меня есть следующая настройка:
Пользовательская публикация альфа:
- отправить изображение на сервер приложений через конечные точки
- сервер хранит изображение в хранилище blob.
- сервер хранит blobkey в хранилище данных
Пользователь Bravo получает:
- сервер получает blobkey из хранилища данных
- сервер получает изображение с помощью клавиши blob
- сервер отправляет изображение в приложение Android с помощью конечных точек
Эта настройка занимает два (2) минуты, когда мое приложение Android отправляет изображение, когда я вижу его в язве blob. Излишне говорить, что это совершенно неприемлемо.
Мой сервер обрабатывает изображение программно, используя следующий код:
public static BlobKey toBlobstore(Blob imageData) throws FileNotFoundException, FinalizationException, LockException, IOException {
if (null == imageData)
return null;
// Get a file service
FileService fileService = FileServiceFactory.getFileService();
// Create a new Blob file with mime-type "image/png"
AppEngineFile file = fileService.createNewBlobFile("image/jpeg");// png
// Open a channel to write to it
boolean lock = true;
FileWriteChannel writeChannel = fileService.openWriteChannel(file, lock);
// This time we write to the channel directly
writeChannel.write(ByteBuffer.wrap
(imageData.getBytes()));
// Now finalize
writeChannel.closeFinally();
return fileService.getBlobKey(file);
}
Кто-нибудь знает, как я могу либо приспособить официальный пример для использования конечных точек (в случае, когда я должен использовать экземпляры приложений для приложения), либо использовать getServingUrl
(минуя мои экземпляры), чтобы хранить и обслуживать мои капли?
Пожалуйста, вместо слов, введите код. Спасибо.
Ответы
Ответ 1
Я расскажу, как я это делаю. Я не использую google-cloud-endpoints, а только собственный собственный api на основе отдыха, но в любом случае это должна быть одна и та же идея.
Я выложу шаг за шагом с кодом, надеюсь, это будет ясно.
Вы просто адаптируете способ отправки своих запросов для использования конечных точек вместо того, чтобы делать его более общим, как в этом примере. Я включаю в себя некоторые шаблоны, но исключаю try/catch, проверку ошибок и т.д. Для краткости.
Шаг 1 (клиент)
Первый клиент запрашивает URL-адрес загрузки с сервера:
HttpClient httpclient = new DefaultHttpClient();
HttpConnectionParams.setConnectionTimeout(httpclient.getParams(), 10000); //Timeout Limit
HttpGet httpGet = new HttpGet("http://example.com/blob/getuploadurl");
response = httpclient.execute(httpGet);
Шаг 2 (сервер)
На стороне сервера сервлет загрузки будет выглядеть примерно так:
String blobUploadUrl = blobstoreService.createUploadUrl("/blob/upload");
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType("text/plain");
PrintWriter out = res.getWriter();
out.print(blobUploadUrl);
out.flush();
out.close();
Обратите внимание на аргумент createUploadUrl. Здесь клиент будет
перенаправляется после завершения фактической загрузки. Что там
вы будете обрабатывать сохранение blobkey и/или обслуживающего URL-адреса и возвращать его клиенту. Вам нужно будет отобразить сервлет на этот URL-адрес, который будет обрабатывать шаг 4
Шаг 3 (клиент)
Вернитесь к клиенту еще раз, чтобы отправить фактический файл на URL-адрес загрузки, используя URL-адрес, возвращенный с шага 2.
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(uploadUrlReturnedFromStep2);
FileBody fileBody = new FileBody(thumbnailFile);
MultipartEntity reqEntity = new MultipartEntity();
reqEntity.addPart("file", fileBody);
httppost.setEntity(reqEntity);
HttpResponse response = httpclient.execute(httppost)
Как только этот запрос будет отправлен на сервлет на шаге 2, он будет перенаправлен на сервлет, указанный вами в createUploadUrl()
ранее
Шаг 4 (сервер)
Назад на сервер:
Это сервлет, обрабатывающий URL-адрес, отображаемый на blob/upload
. Мы вернем blobkey и обслуживающий URL-адрес клиенту в объекте json:
List<BlobKey> blobs = blobstoreService.getUploads(req).get("file");
BlobKey blobKey = blobs.get(0);
ImagesService imagesService = ImagesServiceFactory.getImagesService();
ServingUrlOptions servingOptions = ServingUrlOptions.Builder.withBlobKey(blobKey);
String servingUrl = imagesService.getServingUrl(servingOptions);
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType("application/json");
JSONObject json = new JSONObject();
json.put("servingUrl", servingUrl);
json.put("blobKey", blobKey.getKeyString());
PrintWriter out = res.getWriter();
out.print(json.toString());
out.flush();
out.close();
Шаг 5 (клиент)
Мы получим blobkey и обслуживающий URL из json, а затем отправим его вместе с идентификатором пользователя и т.д. для хранения в объекте хранилища данных.
JSONObject resultJson = new JSONObject(resultJsonString);
String blobKey = resultJson.getString("blobKey");
String servingUrl = resultJson.getString("servingUrl");
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("userId", userId));
nameValuePairs.add(new BasicNameValuePair("blobKey",blobKey));
nameValuePairs.add(new BasicNameValuePair("servingUrl",servingUrl));
HttpClient httpclient = new DefaultHttpClient();
HttpConnectionParams.setConnectionTimeout(httpclient.getParams(), 10000);
HttpPost httppost = new HttpPost(url);
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
// Continue to store the (immediately available) serving url in local storage f.ex
Шаг 6 (сервер)
Фактически сохраняя все в хранилище данных (используя в этом примере объективирование)
final String userId = req.getParameter("userId");
final String blobKey = req.getParameter("blobKey");
final String servingUrl = req.getParameter("servingUrl");
ExampleEntity entity = new ExampleEntity();
entity.setUserId(userId);
entity.setBlobKey(blobKey);
entity.setServingUrl(servingUrl);
ofy().save().entity(entity);
Надеюсь, это упростит ситуацию. Если кто-то хочет отредактировать ответ, чтобы использовать конечные точки облака вместо этого более общего примера, не стесняйтесь:)
Об обслуживающем URL
Сервисный URL - отличный способ подавать изображения вашим клиентам, поскольку он может динамически масштабировать изображения на лету. Например, вы можете отправлять меньшие изображения своим пользователям LDPI, просто добавив =sXXX
в конец обслуживающего URL-адреса. Где XXX - размер пикселя самого большого размера вашего изображения. Вы полностью избегаете своих экземпляров и платите только за пропускную способность, а пользователь загружает только то, что ей нужно.
PS
На шаге 4 нужно остановиться и просто сохранить его прямо там, пройдя по userId f.ex на шаге 3. Любые параметры должны быть отправлены на шаг 4, но я не получил этого, чтобы работать, так вот как я это делаю в данный момент, поэтому я делюсь этим так, потому что знаю, что это работает.
Ответ 2
Я использовал ответ на этот вопрос для создания собственной системы, использующей конечные точки AppEngine. В отличие от сообщений выше, я хочу иметь чистый API, который напрямую передает изображение (как массив байтов) в конечную точку Google, а загрузка в BlobstorageService выполняется на стороне сервера. Преимущество этого в том, что у меня есть атомный API. Недостатком, очевидно, является загрузка на сервер, а также тяжелые операции сортировки на клиенте.
Android - загрузка, масштабирование и сериализация изображения и загрузка в конечные точки
void uploadImageBackground(Bitmap bitmap) throws IOException {
// Important! you wanna rescale your bitmap (e.g. with Bitmap.createScaledBitmap)
// as with full-size pictures the base64 representation would not fit in memory
// encode bitmap into byte array (very resource-wasteful!)
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
bitmap.recycle();
bitmap = null;
stream = null;
// Note: We encode ourselves, instead of using image.encodeImageData, as this would throw
// an 'Illegal character '_' in base64 content' exception
// See: http://stackoverflow.com/questions/22029170/upload-photos-from-android-app-to-google-cloud-storage-app-engine-illegal-char
String base64 = Base64.encodeToString(byteArray, Base64.DEFAULT);
byteArray = null;
// Upload via AppEngine Endpoint (ImageUploadRequest is a generated model)
ImageUploadRequest image = new ImageUploadRequest();
image.setImageData(base64);
image.setFileName("picture.png");
image.setMimeType("image/png");
App.getMyApi().setImage(image).execute();
}
Конечная точка API Backend - Загрузить изображение в BlobstorageService
@ApiMethod(
name = "setImage",
path = "setImage",
httpMethod = ApiMethod.HttpMethod.POST
)
public void saveFoodImageForUser(ImageUploadRequest imageRequest) throws IOException {
assertNotEmpty(userId, "userId");
assertNotNull(imageRequest, "imageRequest");
// create blob url
BlobstorageService blobService = BlobstoreServiceFactory.getBlobstoreService();
String uploadUrl = blobService.createUploadUrl("/blob/upload");
// create multipart body containing file
HttpEntity requestEntity = MultipartEntityBuilder.create()
.addBinaryBody("file", imageRequest.getImageData(),
ContentType.create(imageRequest.getMimeType()), imageRequest.getFileName())
.build();
// Post request to BlobstorageService
// Note: We cannot use Apache HttpClient, since AppEngine only supports Url-Fetch
// See: https://cloud.google.com/appengine/docs/java/sockets/
URL url = new URL(uploadUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.addRequestProperty("Content-length", requestEntity.getContentLength() + "");
connection.addRequestProperty(requestEntity.getContentType().getName(), requestEntity.getContentType().getValue());
requestEntity.writeTo(connection.getOutputStream());
// BlobstorageService will forward to /blob/upload, which returns our json
String responseBody = IOUtils.toString(connection.getInputStream());
if(connection.getResponseCode() < 200 || connection.getResponseCode() >= 400) {
throw new IOException("HTTP Status " + connection.getResponseCode() + ": " + connection.getHeaderFields() + "\n" + responseBody);
}
// parse BlopUploadServlet Json response
ImageUploadResponse response = new Gson().fromJson(responseBody, ImageUploadResponse.class);
// save blobkey and serving url ...
}
Сервлет, который обрабатывает обратный вызов из BlobstorageService
public class BlobUploadServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
BlobstorageService blobService = BlobstoreServiceFactory.getBlobstoreService();
List<BlobKey> blobs = blobService.getUploads(req).get("file");
if(blobs == null || blobs.isEmpty()) throw new IllegalArgumentException("No blobs given");
BlobKey blobKey = blobs.get(0);
ImagesService imagesService = ImagesServiceFactory.getImagesService();
ServingUrlOptions servingOptions = ServingUrlOptions.Builder.withBlobKey(blobKey);
String servingUrl = imagesService.getServingUrl(servingOptions);
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType("application/json");
// send simple json response (ImageUploadResponse is a POJO)
ImageUploadResponse result = new ImageUploadResponse();
result.setBlobKey(blobKey.getKeyString());
result.setServingUrl(servingUrl);
PrintWriter out = res.getWriter();
out.print(new Gson().toJson(result));
out.flush();
out.close();
}
}
Осталось только привязать /blob/upload
к UploadBlobServlet.
Примечание. Это не работает, когда AppEngine работает локально (если выполняется локально, тогда POST в BlobstorageService всегда будет возвращать 404 NOT FOUND)
Ответ 3
Поскольку я пробовал много способов сделать службу обратного вызова в api конечной точки, я отменяю этот aproach. Однако я мог бы решить эту проблему, сделав параллельный сервлет конечной точке api, ему нужно только определить сервер класса и добавить его в конфигурацию web.xml. Здесь мое решение:
1 Служба Enpoint для получения URL для загрузки:
Затем служебный coudl должен быть защищен clientId
@ApiMethod(name = "getUploadURL", httpMethod = HttpMethod.GET)
public Debug getUploadURL() {
String blobUploadUrl = blobstoreService.createUploadUrl("/update");
Debug debug = new Debug();
debug.setData(blobUploadUrl);
return debug;
}
2. Теперь клиент может позвонить в конечную точку для получения URL-адреса для загрузки:
Может быть, некоторые вроде этого (для android тоже используйте вашу клиентскую библиотеку):
gapi.client.debugendpoint.getUploadURL().execute();
3. Следующим шагом будет toto сообщение для URL, зашедшего на последнем шаге:
Вы можете сделать это с помощью httpClient android, опять же, в моем случае мне нужно загрузить из Интернета, затем я использую форму и обратный вызов события onChangeFile() для получения uploadurl (используя шаг 3), затем, когда он отвечает на изменение формы параметры "действие" и "codeId" перед тем, как кто-то решит нажать кнопку "Отправить":
<form id="submitForm" action="put_here_uploadUrl" method="post" enctype="multipart/form-data">
<input type="file" name="image" onchange="onChangeFile()">
<input type="text" name="codeId" value='put_here_some_dataId'>
<input type="submit" value="Submit"></form>
4 Наконец, класс сервлетов paralele:
@SuppressWarnings("serial")
public class Update extends HttpServlet{
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String userId = req.getParameter("codeId");
List<BlobKey> blobs = BSF.getService().getUploads(req).get("image");
BlobKey blobKey = blobs.get(0);
ImagesService imagesService = ImagesServiceFactory.getImagesService();
ServingUrlOptions servingOptions = ServingUrlOptions.Builder.withBlobKey(blobKey);
String servingUrl = imagesService.getServingUrl(servingOptions);
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("application/json");
JSONObject json = new JSONObject();
try {
json.put("imageUrl", servingUrl);
json.put("codeId", "picture_of_"+userId);
json.put("blobKey", blobKey.getKeyString());
} catch (JSONException e){
e.printStackTrace();
}
PrintWriter out = resp.getWriter();
out.print(json.toString());
out.flush();
out.close();
}
}
и добавьте в web.xml, где com.apppack - пакет обновления класса
<servlet>
<servlet-name>update</servlet-name>
<servlet-class>com.apppack.Update</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>update</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>