title: Google Cloud Vision OCR Returns "Bad Image Data" on Google Cloud Shell Localhost
body: <p><strong>tl;dr:</strong> How can I get Google Cloud Vision OCR to work on Cloud Shell editor's localhost?</p>
  <p>I'm using <a href="https://cloud.google.com/shell/docs/viewing-and-editing-files" rel="nofollow noreferrer">Google Cloud Shell editor</a>, which contains a <a href="https://cloud.google.com/shell/docs/using-web-preview" rel="nofollow noreferrer">web preview</a> feature that serves &quot;local&quot; webservers at URLs like <code>https://8080-dot-10727374-dot-devshell.appspot.com/index.html</code>.</p>
  <p>I'm following <a href="https://cloud.google.com/vision/docs/ocr" rel="nofollow noreferrer">this tutorial</a> for Cloud Vision OCR. I put that example code into a servlet that uses Blobstore as an image host:</p>
  <pre class="lang-java prettyprint-override"><code>package com.google.servlets;
  
  import com.google.appengine.api.blobstore.BlobInfo;
  import com.google.appengine.api.blobstore.BlobInfoFactory;
  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.images.ImagesService;
  import com.google.appengine.api.images.ImagesServiceFactory;
  import com.google.appengine.api.images.ServingUrlOptions;
  import com.google.cloud.vision.v1.AnnotateImageRequest;
  import com.google.cloud.vision.v1.AnnotateImageResponse;
  import com.google.cloud.vision.v1.BatchAnnotateImagesResponse;
  import com.google.cloud.vision.v1.Feature;
  import com.google.cloud.vision.v1.Image;
  import com.google.cloud.vision.v1.ImageAnnotatorClient;
  import com.google.cloud.vision.v1.ImageSource;
  import com.google.cloud.vision.v1.TextAnnotation;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  import javax.servlet.annotation.WebServlet;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  /**
   * When the user submits the form, Blobstore processes the file upload and then forwards the request
   * to this servlet. This servlet can then analyze the image using the Vision API.
   */
  @WebServlet(&quot;/image-analysis&quot;)
  public class ImageAnalysisServlet extends HttpServlet {
  
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
  
      PrintWriter out = response.getWriter();
  
      // Get the BlobKey that points to the image uploaded by the user.
      BlobKey blobKey = getBlobKey(request, &quot;image&quot;);
  
      // Get the URL of the image that the user uploaded.
      String imageUrl = getUploadedFileUrl(blobKey);
  
     // Extract text from the image
      String text = detectDocumentText(imageUrl);
  
      // Output some HTML.
      response.setContentType(&quot;text/html&quot;);
      out.println(&quot;&lt;p&gt;Here's the image you uploaded:&lt;/p&gt;&quot;);
      out.println(&quot;&lt;a href=\&quot;&quot; + imageUrl + &quot;\&quot;&gt;&quot;);
      out.println(&quot;&lt;img src=\&quot;&quot; + imageUrl + &quot;\&quot; /&gt;&quot;);
      out.println(&quot;&lt;/a&gt;&quot;);
      out.println(&quot;&lt;h1&gt;text: &quot; + text + &quot;&lt;/h1&gt;&quot;);
    }
  
      /**
     * Returns the BlobKey that points to the file uploaded by the user, or null if the user didn't
     * upload a file.
     */
    private BlobKey getBlobKey(HttpServletRequest request, String formInputElementName) {
      BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
      Map&lt;String, List&lt;BlobKey&gt;&gt; blobs = blobstoreService.getUploads(request);
      List&lt;BlobKey&gt; blobKeys = blobs.get(&quot;image&quot;);
  
      // User submitted form without selecting a file, so we can't get a BlobKey. (dev server)
      if (blobKeys == null || blobKeys.isEmpty()) {
        return null;
      }
  
      // Our form only contains a single file input, so get the first index.
      BlobKey blobKey = blobKeys.get(0);
  
      // User submitted form without selecting a file, so the BlobKey is empty. (live server)
      BlobInfo blobInfo = new BlobInfoFactory().loadBlobInfo(blobKey);
      if (blobInfo.getSize() == 0) {
        blobstoreService.delete(blobKey);
        return null;
      }
  
      return blobKey;
    }
  
    /** Returns a URL that points to the uploaded file. */
    private String getUploadedFileUrl(BlobKey blobKey) {
      ImagesService imagesService = ImagesServiceFactory.getImagesService();
      ServingUrlOptions options = ServingUrlOptions.Builder.withBlobKey(blobKey);
      String url = imagesService.getServingUrl(options);
  
      // GCS's localhost preview is not actually on localhost,
      // so make the URL relative to the current domain.
      if(url.startsWith(&quot;http://localhost:8080/&quot;)){
        url = url.replace(&quot;http://localhost:8080/&quot;, &quot;https://8080-dot-10727374-dot-devshell.appspot.com/&quot;);
      }
  
      return url;
    }
  
    private String detectDocumentText(String path) throws IOException {
      List&lt;AnnotateImageRequest&gt; requests = new ArrayList&lt;&gt;();
      ImageSource imgSource = ImageSource.newBuilder().setImageUri(path).build(); 
      Image img = Image.newBuilder().setSource(imgSource).build();
      Feature feat = Feature.newBuilder().setType(Feature.Type.DOCUMENT_TEXT_DETECTION).build();
      AnnotateImageRequest request = AnnotateImageRequest.newBuilder().addFeatures(feat).setImage(img).build();
      requests.add(request);
  
      // Initialize client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests. After completing all of your requests, call
      // the &quot;close&quot; method on the client to safely clean up any remaining background resources.
      try (ImageAnnotatorClient client = ImageAnnotatorClient.create()) {
        BatchAnnotateImagesResponse response = client.batchAnnotateImages(requests);
        List&lt;AnnotateImageResponse&gt; responses = response.getResponsesList();
        client.close();
        
        // Check to see if any of the responses are errors
        for (AnnotateImageResponse res : responses) {
          if (res.hasError()) {
            System.out.format(&quot;Error: %s%n&quot;, res.getError().getMessage());
            return &quot;Error: &quot; + res.getError().getMessage();
          }
      
          // For full list of available annotations, see http://g.co/cloud/vision/docs
          TextAnnotation annotation = res.getFullTextAnnotation();
          return annotation.getText();
        }
      }
      catch(Exception e) {
        return &quot;ERROR: ImageAnnotatorClient Failed, &quot; + e;
      }
      // Case where the ImageAnnotatorClient works, but there are no responses from it.
      return &quot;Error: No responses&quot;;
    }
  }
  </code></pre>
  <p>When I deploy to a <strong>real</strong> server using the <code>mvn package appengine:deploy</code> command, this works perfectly:</p>
  <p><img src="https://i.stack.imgur.com/hzmMR.png" alt="live server" /></p>
  <p>(Well, as perfectly as can be expected from this test image.)</p>
  <p>However, if I deploy to a &quot;local&quot; devserver using the <code>mvn package appengine:run</code> command, then Google Cloud Vision returns a generic &quot;Bad image data&quot; error:</p>
  <p><img src="https://i.stack.imgur.com/CSlwI.png" alt="devserver" /></p>
  <p>I'm guessing this is because the image URL (<code>https://8080-dot-10727374-dot-devshell.appspot.com/_cloudshellProxy/_ah/img/TjxgeYiHlCkix-XRj94jnw</code>) is not publicly accessible, because it's running on a &quot;fake&quot; localhost that requires me to be logged into my Google account to see.</p>
  <p>How can I get Google Cloud Vision OCR to work on Cloud Shell editor's &quot;fake&quot; localhost?</p>