32

Pracuję nad projektem android związanym z aplikacją przy użyciu wtyczki eclipse. Jednym z aspektów aplikacji jest umożliwienie użytkownikowi Alfa wysyłania zdjęć do użytkownika Bravo. Aby to zrobić, że mam następującą konfigurację:przy użyciu blobstore z punktem końcowym chmury Google i Androidem

użytkownika Alpha publikacji:

  • wyślij zdjęcie do mojego app serwera silnika poprzez końcowych
  • serwer zapisuje obraz w sklepie blob
  • serwer przechowuje blobkey w magazynie danych

użytkownika Bravo się:

  • serwer dostaje blobkey z magazynu danych
  • serwer pobiera obraz za pomocą blob klucza
  • serwer wysyła obraz do aplikacji dla Androida z wykorzystaniem punktów końcowych

Taka konfiguracja trwa w górę z dwóch (2) minut od kiedy moja android aplikacja wysyła obraz do kiedy widzę go w bólu rany. Nie trzeba dodawać, że jest to całkowicie nie do przyjęcia.

Serwer przetwarza obraz programowo, thru następującego kodu:

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); 
    } 

Czy ktoś wie jak mogę albo dostosować the official example użyć punktów końcowych (w przypadku, gdy muszę używać moich wystąpień app-engine) lub użyć getServingUrl (z pominięciem moich instancji) do przechowywania i obsługi moich obiektów typu blob?
Zamiast słów, proszę podać kod. Dzięki.

+0

Byłem fiddeling z siebie za Blobstore czasami. Nie sprawiło mi to, że działało zgodnie z moimi upodobaniami i ostatecznie skończyło się na używaniu interfejsu API do wymiany zdjęć między użytkownikami. Bardzo łatwe do wdrożenia i działa jak urok. Może to również opcja dla ciebie ... – pumpkee

+3

Przyjęta odpowiedź jest naprawdę świetną odpowiedzią, ale byłoby znacznie lepiej, gdyby ktoś mógł dać odpowiedź punktu końcowego. W szczególności, ** jak mogę uzyskać URL wywołania zwrotnego, aby wskazać metodę punktu końcowego? ** Jeśli ktoś odpowie, że dla mnie, byłoby świetnie. – user3093402

Odpowiedz

32

Podzielę się tym, jak to robię. Nie używam punktów końcowych w chmurze google, ale tylko mój własny interfejs oparty na odpoczynku, ale powinien to być ten sam pomysł w obu kierunkach.

Przedstawię to krok po kroku za pomocą kodu, mam nadzieję, że będzie jasne. Po prostu dostosowałbyś sposób wysyłania swoich żądań do używania punktów końcowych, zamiast robić to bardziej ogólnym, jak w tym przykładzie. Załączam kilka elementów, ale z wyłączeniem try/catch, sprawdzanie błędów itp. Dla zwięzłości.

Krok 1 (klient)

Pierwszy klient żąda url wysyłania z serwera:

HttpClient httpclient = new DefaultHttpClient();  
HttpConnectionParams.setConnectionTimeout(httpclient.getParams(), 10000); //Timeout Limit 

HttpGet httpGet = new HttpGet("http://example.com/blob/getuploadurl"); 
response = httpclient.execute(httpGet); 

Krok 2 (serwer)

Po stronie serwera przesyłanie żądania serwletu wyglądałby tak:

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(); 

zanotuj argument do createUploadUrl. To jest miejsce, w którym klient zostanie przekierowany po zakończeniu faktycznego przesyłania. To jest miejsce, gdzie zajmujesz się przechowywaniem blobkey i/lub wyświetlania adresu URL i zwracaniem go do klienta.musisz mapować serwletu do tej zawartości, która będzie obsługiwać krok 4

Krok 3 (klient) Powrót do klienta ponownie wysłać rzeczywisty plik do przesłania za pomocą adresu URL url wrócił z kroku 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) 

Gdy to żądanie jest wysyłane do serwletu w punkcie 2, zostanie on przekierowany do serwletu określonej w createUploadUrl() wcześniejszym

Krok 4 (serwer)

Powrót do strony serwera: To jest serwlet obsługujący adres URL mapowany na blob/upload. Będziemy tu zwrócić blobkey i serwowania URL do klienta w obiekcie 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(); 

Krok 5 (klient)

Będziemy uzyskać blobkey i serwowania URL z JSON, a następnie wysłać wraz z identyfikatorem użytkownika itp. do przechowywania w encji magazynu danych.

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 

Krok 6 (serwer) Właściwie przechowywania wszystko w magazynie danych (za pomocą zobiektywizować w tym przykładzie)

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); 

Mam nadzieję, że to sprawia, że ​​rzeczy bardziej jasne. Jeśli ktoś chce zmieniać odpowiedź na używanie chmurze punktów końcowych zamiast tego bardziej ogólny przykład, nie krępuj się :)

O obsługującej url

Obsługująca url Jest to świetny sposób, aby służyć swoim klientom obrazów, ze względu na sposób dynamicznego skalowania obrazów w locie. Na przykład możesz wysyłać mniejsze obrazy do swoich użytkowników LDPI, dołączając po prostu na końcu adresu URL do obsługi =sXXX. Gdzie XXX to piksel o największym rozmiarze obrazu. Całkowicie unikasz instancji i płacisz tylko za przepustowość, a użytkownik pobiera tylko to, czego potrzebuje.

PS!

Powinno być możliwe zatrzymanie się w kroku 4 i po prostu zapisać go tam, przesyłając wzdłuż userId f.ex w kroku 3. Wszelkie parametry mają zostać przesłane do kroku 4, ale nie dostałem tego do pracy, więc tak to robię w tej chwili, więc dzielę się tym w ten sposób, ponieważ wiem, że to działa.

+0

Świetna odpowiedź! to mi bardzo pomaga! Mam tylko jedno pytanie. adres URL w metodzie createUploadUrl() jest względny lub absolutny? Mam na myśli dla próbki mój servelet ma ten URL: http://192.168.0.6:8888/1/api/blobs/upload, co mam napisać w parametrze createUploadUrl()? cała ścieżka? – FlavienBert

+0

@FlavienBert Nie wiem, czy bezwzględny adres URL będzie działał, ale względny działa na pewno. Po prostu spróbuj i zobacz :) – Joachim

+0

Udało mi się zrobić to w czterech krokach, jeśli ktokolwiek jest zainteresowany, opublikuję rozwiązanie. Bez tego tutoriala nic by się jednak nie stało ... dzięki Joachim !! –

2

Ponieważ próbowałem z wieloma sposobami wykonywać usługę zwrotną w interfejsie API punktu końcowego, przerwałam to podejście. Mógłbym jednak rozwiązać ten problem, tworząc równoległy serwlet do punktu końcowego api, wystarczy tylko zdefiniować serwer klasy i dodać konfigurację web.xml. Oto moje rozwiązanie:

1 Enpoint serwisowy uzyskać adres URL do wysłania: Następnie usługa coudl być zabezpieczone 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.Teraz klient może zadzwonić do punktu końcowego dla uzyskania URL Dodano:
Może niektórzy tak (dla android cię wykorzystać klient enpoint biblioteka zbyt):

gapi.client.debugendpoint.getUploadURL().execute(); 

3. Kolejnym krokiem jest todo postu do adresu catched in last step: Możesz to zrobić z httpClient z Androidem, znowu, w moim przypadku potrzebuję upload z sieci, a następnie używam formularza i wywołania zwrotnego zdarzenia onChangeFile(), aby pobrać uploadurl (używając kroku 3), a następnie kiedy odpowiedź na zmianę parametrów formularza "akcja" i "codeId", zanim ktoś zdecyduje, kliknij przycisk przesyłania:

<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 Wreszcie paralele klasy servlet:

@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(); 
    } 
} 

i dodać do web.xml, gdzie com.apppack jest pakiet aktualizacji Klasa

<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> 
5

użyłem odpowiedzi na to pytanie do zbudowania własnego systemu, który używa punktów końcowych AppEngine. W przeciwieństwie do powyższych postów, chcę mieć czysty interfejs API, który bezpośrednio transmituje obraz (jako tablicę bajtów) do Google Endpoint, a przesyłanie do BlobstorageService odbywa się po stronie zaplecza. Zaletą tego jest to, że mam atomowy interfejs API. Wadą jest oczywiście obciążenie serwera, a także ciężkie operacje rozrządowe na kliencie.

Android - obciążenie, skala i szeregować zdjęcie i przesłać do punktów końcowych

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(); 
} 

Backend API Endpoint - Prześlij zdjęcie do 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's Json response 
    ImageUploadResponse response = new Gson().fromJson(responseBody, ImageUploadResponse.class); 

    // save blobkey and serving url ... 
} 

serwletu, który obsługuje wywołania zwrotnego z 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(); 
    } 
} 

Pozostaje tylko powiązać /blob/upload z UploadBlobServlet.

Uwaga: To nie wydaje się działać, gdy AppEngine działa lokalnie (jeśli jest wykonywane lokalnie, a następnie na stanowisku, na BlobstorageService zawsze zwróci 404 nie znaleziono)

+0

jak powiązać "/ blob/upload" z UploadBlobServlet.? –

+0

Czy nie ma 1mb limitu rozmiaru obrazu, który można przesłać za pomocą metody punktu końcowego? Dziękujemy za udostępnienie kodu. – Micro

+0

@MicroR Brak limitu 10 MB. https://cloud.google.com/datastore/docs/concepts/limits –

Powiązane problemy