【问题标题】:http 400 error uploading file to blobstore with google app engine使用谷歌应用引擎将文件上传到 blobstore 时出现 http 400 错误
【发布时间】:2014-07-11 00:28:48
【问题描述】:

我正在尝试使用 google app engine blobstore 上传文件。

它应该像这样工作:调用 BlobstoreService 来获取上传 url,提供回调 URL。客户端被重定向到上传 URL,发送数据,然后当他们完成后,他们将被重定向到回调 URL,并带有几个代表 blobstore 键的参数。

正如我们将看到的,开发服务器的行为与生产服务器不同。

在生产环境中,我的代码完成了上传,然后我没有重定向回我的回调 URL,而是收到 400 错误响应(暗示我的请求有问题)。如何在生产中调试它?我不知道如何打开 blobstore 的日志记录。

所以我尝试在本地针对开发服务器运行它。这一次,如果我不设置“内容长度”属性,我会得到 411(未设置内容长度)。但是,如果我尝试设置该属性,我会得到“IllegalStateException:已连接”。这些异常都不会发生在生产中。

所以我不知道下一步该去哪里。我需要让它对 dev 工作,希望我可以在本地调试 blobstore,或者找出它为什么不能在生产中的 blobstore 上工作。

    public void upload(String uri, File file) throws IOException    {
    HttpURLConnection conn=null;
    HttpURLConnection conn2=null;
    FileInputStream fileInputStream = null;
    DataOutputStream dos=null;
    try {
        String lineEnd = "\r\n";
        String twoHyphens = "--";
        String boundary = "*****";
        int bytesRead, bytesAvailable, bufferSize;
        byte[] buffer;
        int maxBufferSize = 1 * 1024 * 1024; 
        // open a URL connection to the Servlet
        fileInputStream = new FileInputStream(file);
        URL url = new URL(uri);

        // Open a HTTP  connection to  the URL
        conn = (HttpURLConnection) url.openConnection();
        conn.setDoInput(true); // Allow Inputs
        conn.setDoOutput(true); // Allow Outputs
        conn.setUseCaches(false); // Don't use a Cached Copy
        conn.setInstanceFollowRedirects(false);
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Connection", "Keep-Alive");
        conn.setRequestProperty("ENCTYPE", "multipart/form-data");
        conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
//          conn.setChunkedStreamingMode(1024);
        conn.setRequestProperty("content-length", String.valueOf(file.length()));   //caused IllegalStateException "Already connected" locally, but not remotely

        log("Orignal URL: " + conn.getURL());
        //conn.connect();   //TODO duplicates url.openConnection() above?
        conn.getInputStream();  //so we can follow the redirect
        String redirectedUrl = conn.getHeaderField("Location");
        log("Redirected URL: " + redirectedUrl);
        //this is horrible and messy but let's get it working then clean it up later

        conn.disconnect();
        url = new URL(redirectedUrl);

        // Open a new HTTP  connection to  the URL
        conn2 = (HttpURLConnection) url.openConnection();
        conn2.setDoInput(true); // Allow Inputs
        conn2.setDoOutput(true); // Allow Outputs
        conn2.setUseCaches(false); // Don't use a Cached Copy
        conn2.setInstanceFollowRedirects(false);
        conn2.setRequestMethod("POST");
        conn2.setRequestProperty("Connection", "Keep-Alive");
        conn2.setRequestProperty("ENCTYPE", "multipart/form-data");
        conn2.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
        conn2.setChunkedStreamingMode(maxBufferSize);
        conn2.setRequestProperty("Content-Length", String.valueOf(file.length()));
        conn2.connect();
        dos = new DataOutputStream(conn2.getOutputStream());

        dos.writeBytes(twoHyphens + boundary + lineEnd);
        dos.writeBytes("Content-Disposition: form-data; name=\"myFile\";filename=\""+file.getName()+"\"" + lineEnd);
        dos.writeBytes(lineEnd);

        // create a buffer of  maximum size
        bytesAvailable = fileInputStream.available();

        bufferSize = Math.min(bytesAvailable, maxBufferSize);
        buffer = new byte[bufferSize];

        // read file and write it into form...
        bytesRead = fileInputStream.read(buffer, 0, bufferSize); 

        while (bytesRead > 0) {
            dos.write(buffer, 0, bufferSize);
            bytesAvailable = fileInputStream.available();
            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            bytesRead = fileInputStream.read(buffer, 0, bufferSize);  
        }

        // send multipart form data necesssary after file data...
        dos.writeBytes(lineEnd);
        dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

        // Responses from the server (code and message)
        int serverResponseCode = conn2.getResponseCode();
        String serverResponseMessage = conn2.getResponseMessage();
        //we are expecting another redirect here
        log("aaaargh! 400 instead of 302! "+serverResponseCode+" to "+conn2.getHeaderField("Location"));
    }catch (IOException e)  {
        log(e.getMessage());
        throw e;
    }catch(Exception e) {
        log(e.getMessage());
        throw new IOException(e);
    }   finally {
        //close the streams //
        if (dos!=null)  {   
            try {
                dos.flush();
                dos.close();
            }catch(IOException ioe){}
        }
        if (fileInputStream!=null)
            try {
                fileInputStream.close();
            }catch(IOException ioe){}

        if (conn!=null )
            try {
                conn.disconnect();
            }catch(Exception ioe){}

        if (conn2!=null)
            try {
                conn2.disconnect();
            }catch(Exception ioe){}

    }
}

注意,上面的 serverResponseMessage 字符串作为“错误请求”从生产 blobstore 返回

【问题讨论】:

  • 正如您所说:您向客户端提供一个上传 URL,然后收到一个简单的回调。这段代码是干什么用的?
  • 我正在尝试使用 google 应用引擎 blobstore 从 android 客户端上传文件。
  • 我怀疑您实际上没有处理后端 servlet 或控制器的确切名称-值对参数。因此,您遇到了这个问题。还要检查您的文件是否已上传到 blobstore。

标签: java google-app-engine http servlets io


【解决方案1】:

我通过使用更高级别的 API 而不是我尝试使用的低级别 HttpConnection 代码解决了这个问题。

    public String uploadFile(String url, File f) throws Exception   {
    HttpClient httpClient = new DefaultHttpClient();    
    String str = "";
    HttpPost first = new HttpPost(url);  //a GET should work but I was getting 405
    HttpResponse firstResponse = httpClient.execute(first);
    str = firstResponse.getFirstHeader("Location").getValue();
    //Post image to generated url
    HttpPost second = new HttpPost(str);
    FileBody fileBody = new FileBody(f);
    MultipartEntity reqEntity = new MultipartEntity();
    reqEntity.addPart("file", fileBody);
    second.setEntity(reqEntity);
    httpClient = new DefaultHttpClient();
    HttpResponse secondResponse = httpClient.execute(second); 
    str = secondResponse.getFirstHeader("Location").getValue();
    log(str);
    return str;
}

此方法返回上传图片的服务 URL。

【讨论】:

    【解决方案2】:

    我不知道如何从 Android 客户端执行此操作,但我可以向您展示我们如何从 javascript 客户端执行此操作,因为它可能为您提供一些指导。

    客户端非常简单(这是一个 angular.js 控制器),我们获取要使用的 url 并上传到它

    // model file upload
        $scope.onFileSelect = function ($files) {
            $scope.uploadStatus = 'loading';
            for (var i = 0; i < $files.length; i++) {
                var file = $files[i];
                console.log(file);
    
                $http.get('/endpoints/prototype/' + $scope.prototype.key + '/file/url')
                    .success((function (data) {
                            console.log(data);
    
                            $scope.upload = $upload.upload({
                                url: data,
                                withCredentials: true,
                                file: file
                            })
                            .success(function (data) {
                                console.log(data);
                                $scope.prototype.modelFile = data;
                            })
                            .error(function (data, status) {
                                console.error(data);
                                $scope.uploadStatus = 'error';
                                $scope.uploadError = status;
                            });
                    }))
                    .error((function (data, status) {
                        console.error(data);
                        $scope.uploadStatus = 'error';
                        $scope.uploadError = status;
                    }));
            }
        };
    

    服务器端,我们有一个端点,它返回用于发布的 URL(这是一个 Spring MVC 控制器)。注意我们在createUploadUrl()中指定了成功路径

    @RequestMapping(value = "/endpoints/prototype/{key}/file/url", method = RequestMethod.GET)
    @ResponseBody
    public String getFileUploadUrl(@PathVariable("key") String prototypeKey) {
        final UploadOptions uploadOptions = Builder.withDefaults()
              .googleStorageBucketName("palmyra-prototype-store");
    
        return blobstoreService.createUploadUrl("/endpoints/prototype/" + prototypeKey + "/file", uploadOptions);
    }
    

    终于上传成功后调用的端点

    @RequestMapping(value = "/endpoints/prototype/{key}/file", method = RequestMethod.POST)
    @ResponseBody
    public String handleFileUpload(@PathVariable("key") String prototypeKey, HttpServletRequest request) {
        final Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(request);
        final Map<String, List<FileInfo>> allFileInfos = blobstoreService.getFileInfos(request);
    
        final List<BlobKey> blobKeys = blobs.get(INPUT_FILE_NAME);
        final List<FileInfo> fileInfos = allFileInfos.get(INPUT_FILE_NAME);
    
        if ((blobKeys == null) || (fileInfos == null)) {
            return "You failed to upload a model file for " + prototypeKey + " because the file was empty.";
        } else if ((blobKeys.size() == 1) && (fileInfos.size() == 1)) {
            final BlobKey blobKey = blobKeys.get(0);
            final FileInfo fileInfo = fileInfos.get(0);
    
            final Prototype prototype = prototypeService.findByKey(prototypeKey);
            prototype.setModelFile(blobInfoFactory.loadBlobInfo(blobKey).getFilename());
            prototype.setUploadedModelFile(fileInfo.getGsObjectName());
    
            prototypeService.updatePrototype(prototypeKey, prototype);
    
            return blobInfoFactory.loadBlobInfo(blobKey).getFilename();
        } else {
            return "One and only one file is allowed to be uploaded. The related prototype is " + prototypeKey +
                  ". The number of blobs is " + blobKeys.size() + ", the number of files is " + fileInfos.size();
        }
    }
    

    就是这样,希望它可以帮助您解决您的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-04
      • 2012-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-11
      • 1970-01-01
      相关资源
      最近更新 更多