什么是HttpPost发送到服务器和服务器响应之间的延迟

时间:2017-07-07 14:26:43

标签: java http tomcat

我是将一个zip文件从Java桌面应用程序上传到一个Httpserver(运行Tomcat 7),我正在使用Apache httpClient 4.5.3并显示一个进度条,显示使用此包装器解决方案的进度https://github.com/x2on/gradle-hockeyapp-plugin/blob/master/src/main/groovy/de/felixschulze/gradle/util/ProgressHttpEntityWrapper.groovy

因此,在我的代码中,每次调用回调时都会更新进度条

HttpEntity reqEntity = MultipartEntityBuilder.create()
        .addPart("email", comment)
        .addPart("bin", binaryFile)
        .build();

ProgressHttpEntityWrapper.ProgressCallback progressCallback = new ProgressHttpEntityWrapper.ProgressCallback() {

    @Override
    public void progress(final float progress) {
        SwingUtilities.invokeLater(
                new Runnable()
                {
                    public void run()
                    {
                        MainWindow.logger.severe("progress:"+progress);
                        Counters.getUploadSupport().set((int)progress);
                        SongKong.refreshProgress(CreateAndSendSupportFilesCounters.UPLOAD_SUPPORT_FILES);
                    }
                }
        );
    }
};

httpPost.setEntity(new ProgressHttpEntityWrapper(reqEntity, progressCallback));
HttpResponse response = httpclient.execute(httpPost);
HttpEntity resEntity = response.getEntity();
MainWindow.logger.severe("HttpResponse:"+response.getStatusLine());

这会报告以百分比形式上传的文件,但报告100%创建和实际从服务器接收http状态之间存在相当大的延迟。

07/07/2017 14.23.54:BST:CreateSupportFile$4$1:run:SEVERE: progress:99.19408
07/07/2017 14.23.54:BST:CreateSupportFile$4$1:run:SEVERE: progress:99.40069
07/07/2017 14.23.54:BST:CreateSupportFile$4$1:run:SEVERE: progress:99.6073
07/07/2017 14.23.54:BST:CreateSupportFile$4$1:run:SEVERE: progress:99.81391
07/07/2017 14.23.54:BST:CreateSupportFile$4$1:run:SEVERE: progress:99.99768
07/07/2017 14.23.54:BST:CreateSupportFile$4$1:run:SEVERE: progress:99.99778
07/07/2017 14.23.54:BST:CreateSupportFile$4$1:run:SEVERE: progress:99.99789
07/07/2017 14.23.54:BST:CreateSupportFile$4$1:run:SEVERE: progress:99.999794
07/07/2017 14.23.54:BST:CreateSupportFile$4$1:run:SEVERE: progress:99.9999
07/07/2017 14.23.54:BST:CreateSupportFile$4$1:run:SEVERE: progress:100.0
07/07/2017 14.24.11:BST:CreateSupportFile:sendAsHttpPost:SEVERE: HttpResponse:HTTP/1.1 200 OK
07/07/2017 14.24.11:BST:CreateSupportFile:sendAsHttpPost:SEVERE: Unknown Request

注意不是因为我的tomcat代码做了很多,因为我还没有为这个函数实现tomcat代码所以它只是默认为“Unknown Request”代码。

protected void doPost(javax.servlet.http.HttpServletRequest request, 

    javax.servlet.http.HttpServletResponse response)
                throws javax.servlet.ServletException, java.io.IOException
        {
            String createMacUpdateLicense   = request.getParameter(RequestParameter.CREATEMACUPDATELICENSE.getName());
            if(createMacUpdateLicense!=null)
            {
                createMacUpdateLicense(response, createMacUpdateLicense);
            }
            else
            {
                response.setCharacterEncoding("UTF-8");
                response.setContentType("text/plain; charset=UTF-8; charset=UTF-8");
                response.getWriter().println("Unknown Request");
                response.getWriter().close();
            }
        }

如何在用户完成时更准确地向用户报告

更新 我现在已经完全实现了服务器端,这增加了差异

    @Override
    protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response)
            throws javax.servlet.ServletException, java.io.IOException
    {
        String uploadSupportFiles   = request.getParameter(RequestParameter.UPLOADSUPPORTFILES.getName());
        if(uploadSupportFiles!=null)
        {
            uploadSupportFiles(request, response, uploadSupportFiles);
        }
        else
        {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/plain; charset=UTF-8; charset=UTF-8");
            response.getWriter().println("Unknown Request");
            response.getWriter().close();
        }
    }

private void uploadSupportFiles(HttpServletRequest request, HttpServletResponse response, String email) throws IOException
    {
        Part filePart;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/plain; charset=UTF-8; charset=UTF-8");

        try
        {
            filePart = request.getPart("bin");
            String fileName = getSubmittedFileName(filePart);
            response.getWriter().println(email+":File:" + fileName);

            //Okay now save the zip file somewhere and email notification
            File uploads = new File("/home/jthink/songkongsupport");
            File supportFile = new File(uploads, email+".zip");

            int count =0;
            while(supportFile.exists())
            {
                supportFile = new File(uploads, email+"("+count+").zip");
                count++;
            }
            InputStream input;
            input = filePart.getInputStream();
            Files.copy(input, supportFile.toPath());

            Email.sendAlert("SongKongSupportUploaded:" + supportFile.getName(),  "SongKongSupportUploaded:" + supportFile.getName());
            response.getWriter().close();
        }
        catch(ServletException se)
        {
            response.getWriter().println(email+":"+se.getMessage());
            response.getWriter().close();
        }


    }

2 个答案:

答案 0 :(得分:3)

假设您的服务器端代码只是将上传的文件写入某处并响应类似" DONE"最后,这是发生的事情的粗略时间表:

Bytes written to socket OutputStream
============================|
<--> Buffering              |
    Bytes sent by TCP stack |
    ============================
    <------> Network latency|
            Bytes received by Tomcat
            ============================
                            |           (Tomcat waits for all data to finish uploading
                            |            before handing it out as "parts" for your code)  
                            |            File written to local file on server
                            |            =====
                            |                
                            |                  Response "DONE" written by servlet to socket output
                            |                  ==
                            |                  <---> Network latency 
                            |                       == Response "DONE" received by client
                            |                         |
                            |                         |
  "100%" for entity wrapper ^             Actual 100% ^
                             Discrepancy
                             <----------------------->
                             "Twilight Zone" : part of discrepancy you cannot do much about.
                             (progress feedback impossible without using much lower level APIs)
                             <--------------------->

衡量标准当然是完全随意的,但它表明有几个因素可以参与这种差异。

您的服务器在收到所有字节后写入文件,但这并没有太大区别。

所以,因素:

  • (客户端)Java I / O层与OS网络堆栈之间的缓冲(可能在多个级别)
  • 网络延迟
  • (服务器端)OS网络堆栈和Java I / O层之间的缓冲(可能在多个级别)
  • 在磁盘上编写(或完成写入)zip文件的时间
  • 打印回复的时间(可忽略不计)
  • 网络延迟
  • (客户端)阅读响应的时间(可忽略不计)

因此,您可以将这种差异考虑在内并调整上传完成情况&#34;步进到总进度的90%,并在得到最终响应时从90跳到100。从0%到90%,用户会看到&#34;正在上传&#34;,一个很好的进度条移动,然后你显示&#34;处理...&#34;,也许有一个悸动,并在完成,跳到100%。

这是许多其他工具的功能。即使我用浏览器下载文件,也有一个小的延迟,下载似乎停留在&#34;几乎&#34;在文件实际可用之前,100%一秒钟(或更多在我的旧计算机上)。

如果&#34;暮光区&#34;时间远远高于你的进度包装器所感知的上传时间,你可能会遇到问题,因此你的问题就是&#34;这种延迟来自何处?&#34; (现在我不知道)。在这种情况下,请提供完整的时间安排(并确保客户端和服务器机器的时钟同步)。

如果 reaaaally 需要更加准确/顺利的进度报告,您需要更多参与设置。您可能需要在服务器端使用更多低级API(例如,不使用@MultipartConfig等),以便让您的服务器执行类似于接收数据时写入磁盘的操作(这会使错误处理更多很难),打印一个点输出并刷新,每写1%的文件写入磁盘(或任何其他类型的进展,只要它在服务器上的实际进度) -侧)。然后,您的客户端将能够逐步读取该响应,并获得准确的进度报告。你可以避免在客户端进行线程化,按顺序执行此操作是可以的:

  • POST数据,报告进度但缩放到90%(即如果包装说50%,则报告45%)
  • 完成后,开始从服务器读取输出,报告91%,95%,无论如何,直到100%。

即便如此,我也不确定是否可以显示所有步骤的进度信息(特别是在100%发送和服务器可能发送的第一个字节之间),所以即使是非常复杂的设置也是如此会没用的(它可能会暂时停留在90%,然后立即转到91/92 / ...... 99/100)。

所以在这一点上它可能不值得。如果您在客户端发送的最后一个字节和收到的响应之间确实有17秒的步长,那么其他内容就会关闭。最初我假设它是用于大量文件,但从那以后你说你的文件高达50MB,所以你可能还有别的东西要看。

答案 1 :(得分:0)

某些服务器端代码可能会根据块数据的表示方式而改变,但概念大致相同。假设您正在上传一个10MB的文件,并且您的块大小设置为1MB。您将向服务器发送10个请求,每个请求包含1MB数据。客户实际上有责任打破这一切。这就是你将在Javascript中做的事情。然后,通过HttpRequest发送每个请求以及有关文件,块号和块数的一些其他数据。我再次使用plupload插件为我处理这个问题,因此一些Request数据可能会在实现之间有所不同。

  

我向您展示的方法是输出JSON的Webservice的一部分   数据回到客户端。然后你的javascript可以解析JSON和   寻找错误或成功消息并采取适当行动。根据   在您的实施中,您发回的数据可能会有所不同。   javascript将最终处理进度条或百分比   或者其他什么,随着它上传成功的块上传而增加它。我的   我的项目的实现让plupload处理所有这些,但是   也许我给你的那篇文章会给你更多的控制权   客户端。

protected void Upload()
{
    HttpPostedFile file = Request.Files[0];
    String relativeFilePath = "uploads/";
    try
    {
        if(file == null)
            throw new Exception("Invalid Request.");
        //plupload uses "chunk" to indicate which chunk number is being sent
        int chunk = (int)Request.Form["chunk"];
        //plupload uses "chunks" to indicate how many total chunks are being sent
        int chunks = (int)Request.Form["chunks"];
        //plupload uses "name" to indicate the original filename for the file being uploaded
        String filename = Request.Form["name"];
        relativeFilePath += filename;


        //Create a File Stream to manage the uploaded chunk using the original filename
        //Note that if chunk == 0, we are using FileMode.Create because it is the first chunk
        //otherwise, we use FileMode.Append to add to the byte array that was previously saved
        using (FileStream fs = new FileStream(Server.MapPath(relativeFilePath), chunk == 0 ? FileMode.Create : FileMode.Append))
        {
           //create the byte array based on the data uploaded and save it to the FileStream
           var buffer = new byte[file.InputStream.Length];
           file.InputStream.Read(buffer, 0, buffer.Length);
           fs.Write(buffer, 0, buffer.Length);
        }

        if((chunks == 0) || ((chunks > 0)&&(chunk == (chunks - 1))))
        {
          //This is final cleanup.  Either there is only 1 chunk because the file size
          //is less than the chunk size or there are multiple chunks and this is the final one
          //At this point the file is already saved and complete, but maybe the path is only
          //temporary and you want to move it to a final location
          //in my code I rename the file to a GUID so that there is never a duplicate file name
          //but that is based on my application's needs
          Response.Write("{\"success\":\"File Upload Complete.\"}");
        }
        else
          Response.Write("{\"success\":\"Chunk "+chunk+" of "+chunks+" uploaded.\"}");
    }
    catch(Exception ex)
    {
        //write a JSON object to the page and HtmlEncode any quotation marks/HTML tags
        Response.Write("{\"error\":\""+HttpContext.Current.Server.HtmlEncode(ex.Message)+"\"});
    }
}
相关问题