【问题标题】:Catch too long form submission捕获太长的表单提交
【发布时间】:2016-01-02 02:33:33
【问题描述】:

我正在开发一个 HTML 页面,该页面具有通过按钮提交的简单 HTML 表单(没什么特别的)。当表单提交和响应时间过长(如果真的回来时)有几种情况。当等待响应时间过长时,如何以触发回调的方式组织表单?我们可以向用户显示一些通知,表明我们的服务器在这种情况下超载。

这是通过表单发送的请求:

POST http://example.com/search HTTP/1.1
Host: example.com
Proxy-Connection: keep-alive
Content-Length: 83
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://example.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://example.com/
Accept-Encoding: gzip, deflate
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: [cookie definition omitted]

[form data omitted]

Proxy-Connection: keep-alive 是否会以某种方式影响流程?谷歌搜索让我找到了https://github.com/caxap/jquery-safeform 插件,但它的目的有点不同。

【问题讨论】:

  • 尝试使用 Ajax 提交代替普通表单提交,这样您就可以使用 setTimeout 函数来显示您想要显示的消息。
  • @Vivek 感谢您的快速回复!我想过这样的方式,但是我怎样才能让它像一个常规的表单提交一样呢? AJAXing 时,我应该手动处理响应。我认为有更简单的方法。
  • 请求超时通常可以在服务器端强制执行。
  • @plalx 谢谢!似乎非常接近我期望的方式。你能描述一下吗?
  • 我不是很了解ASP.Net,但也许是这个msdn.microsoft.com/en-us/library/…

标签: javascript html asp.net asp.net-mvc forms


【解决方案1】:

这取决于您要呈现给用户的 UI 类型。您可以简单地在服务器级别降低超时,如果它不能及时完成响应,它将中止。但是,用户体验非常苛刻,因为他们只会收到一个可能甚至不会来自您的站点的通用超时错误。 (他们必须点击返回或其他操作才能返回您的网站。)

如果您只是想在经过一定时间后显示一条消息,您可以附加到表单的提交事件并使用setTimeout 显示警报或其他内容:

$('#MyForm').on('submit', function () {
    setTimeout(30000, function () { // number is milliseconds
        alert('Sorry this is taking so long.');
    });
});

最后,如果有某种方法可以在服务器端跟踪正在完成的操作的进度,您可以使用 Web 套接字或通过 AJAX 进行长轮询之类的东西来提供进度条或某种状态更新。不过,这有点复杂,需要您进行一些研究。

【讨论】:

  • 感谢您的回复!这种形式伴随着第三方服务的调用,所以我什至无法假设需要多长时间。我试图设置httpRuntime 的executionTimeout,但无法让它工作。这里可能的问题可能是旧的 MVC 版本,所以我将它升级到更新的框架。我认为您提供给我的代码是最合适的解决方案。但是是否有在提醒用户后中止请求的选项?
  • 不是来自客户端。服务器已经接收到请求,并将努力返回响应,直到它在服务器端成功或超时。
【解决方案2】:

有两种方法,我将分别写两个答案。

XMLHttpRequest 进度方法 (适用于现代浏览器)

只需发送数据并从 XMLHttpRequest 读取上传进度:

//-------------upload ------------------


var lastProgressIndex = -1;


//is the file api available in this browser
//only override *if* available.
if (new XMLHttpRequest().upload) {
    $("#upload-files").click(function () {       
        upload_files($("#files-to-upload")[0].files, lastProgressIndex++);
        return false;
    });
    $("#files-to-upload").change(function () {
        upload_files($("#files-to-upload")[0].files, lastProgressIndex++);
        return false;
    });
    $("#upload-files").hide();
}
function resetFormElement(elem) {
    elem.wrap('<form>').closest('form').get(0).reset();
    elem.unwrap();
}

function clear_upload() {
    //https://stackoverflow.com/questions/1043957/clearing-input-type-file-using-jquery
    var upload = $("#files-to-upload");
    //upload.replaceWith(upload = upload.clone(true));
    resetFormElement(upload);
}

//accepts the input.files parameter
function upload_files(filelist) {
    if (typeof filelist !== "undefined") {
        for (var i = 0, l = filelist.length; i < l; i++) {
            upload_file(filelist[i], lastProgressIndex++);
        }
    }
    clear_upload();
}

//each file upload produces a unique POST
function upload_file(file, index) {

    //TODO - vytvor progress bar podle indexu
    $("#progresscontainer").append('<div id="progressbar' + index + '" class="progressbar"><div id="progresslabel' + index + '" class="progressbarlabel"></div></div>')

    var progressBarSelector = "#progressbar" + index;
    var progressLabelSelector = "#progresslabel" + index;
    var fileName = file.name;

    var xhr = new XMLHttpRequest();

    xhr.upload.addEventListener("progress", function (evt) {
        if (evt.lengthComputable) {
            //update the progress bar

            var percent = Math.floor((evt.loaded / evt.total) * 100) + "%";

            //TODO http://www.binaryintellect.net/articles/859d32c8-945d-4e5d-8c89-775388598f62.aspx                  

            $(progressBarSelector).css({
                width: percent
            });
            $(progressLabelSelector).html(fileName + ' ' + percent);
        }
    }, false);

    // File uploaded
    xhr.addEventListener("load", function () {
        $(progressLabelSelector).html(fileName + " uploaded");

        AddImageToGallery(GetFilenameWithoutExt(fileName));

        $(progressBarSelector).fadeOut(500, function () {
            $(progressBarSelector).remove();
        });
    }, false);

    var guid = $("#Identification").val();
    xhr.open("post", "/uploadurl/uploadfile/" + guid, true);

    // Set appropriate headers
    // We're going to use these in the UploadFile method
    // To determine what is being uploaded.
    xhr.setRequestHeader("Content-Type", "multipart/form-data");
    xhr.setRequestHeader("X-File-Name", file.name);
    xhr.setRequestHeader("X-File-Size", file.size);
    xhr.setRequestHeader("X-File-Type", file.type);

    // Send the file
    xhr.send(file);
}

和服务器部分:

private UploadedFile[] RetrieveFileFromRequest()
{
    List<UploadedFile> uploadedFiles = new List<UploadedFile>();


    if (Request.Files.Count > 0)
    { //they're uploading the old way

        for (int i = 0; i < Request.Files.Count; i++)
        {
            var file = Request.Files[0];
            string contentType = file.ContentType;
            string filename = file.FileName;

            UploadedFile uploadedFile = SaveUploadedFile(file.InputStream, file.ContentLength, filename, contentType);

            uploadedFiles.Add(uploadedFile);
        }
    }
    else if (Request.ContentLength > 0)
    {
        string filename = Request.Headers["X-File-Name"];
        string contentType = Request.Headers["X-File-Type"];

        UploadedFile uploadedFile = SaveUploadedFile(Request.InputStream, Request.ContentLength, filename, contentType);
        uploadedFiles.Add(uploadedFile);
    }

    return uploadedFiles.ToArray();
}

这些来源是对original article 的修改。有related stackoverflow question。

【讨论】:

    【解决方案3】:

    当等待响应时间过长时,如何以触发回调的方式组织表单?

    这是基于您的算法。您可以像蛮力计算一样估计时间并在开始执行之前显示结果。如果您开始处理然后在花费太多时间时将其中断,这不是一个优化的解决方案!

    更新:如果你不能像上面那样估计,至少,写一个asynch控制器方法,当一个动作必须执行几个独立的长时间运行的操作时很有用。按照这个:

    1. 使用unobtrusive-ajax 编写 Ajax 表单
    2. 为OnBegin 表单选项定义一个函数以调用timer
    3. 计时器的超时事件将向用户显示另一件事。
    4. 为操作写一个asynch控制器方法 包含一个计时器,用于在需要更多时间时停止处理 比 ??秒。

    【讨论】:

    • 谢谢!处理不是为了防止少数表单的提交,而是处理一个表单的执行时间过长。
    • @kseen 我的第一段是用于处理一个表单的执行时间过长。你应该在行动前估计!
    • 在我的情况下,我无法预测需要多长时间,甚至不知道它是否会有一些有限的结局。
    • @kseen 我在答案中添加了 更新。
    【解决方案4】:

    有两种方法,我将分别写两个答案。

    发送线程/UI线程方式

    您将需要 2 个 javascript“线程”。

    发帖: 首先打开 websocket 并开始向服务器发送数据。在服务器端更新一些变量,发送了多少数据以及剩余多少数据。

    UI 线程: 使用 Ajax 经常询问服务器(JSON 格式的 HTTP 请求很好),发送了多少数据,剩余多少数据并在 javascript 中呈现进度条。

    最后,当 UI 线程请求接收到上传准备好的信息时,使用 jquery 删除进度条并显示消息或重定向页面。

    使用这种方法,您可以轻松显示发送字节数、估计剩余时间、上传速度。

    所以每个 javascript 线程都有相应的服务器线程。并且两个服务器线程与 dataSend 和 dataRemaining 共享结构,具体取决于您的服务器技术。

    您可以在下面找到我上传多张图片并在完成后显示预览的部分代码:

    intervalID = window.setInterval(function() 
    {
        var request = new Sys.Net.WebRequest();
        request.set_url('UploadProgress.ashx?DJUploadStatus=' + token + '&ts=' + new Date().getTime());
        request.set_httpVerb('GET');
        request.add_completed(function(executor) {
    
            //  the progress is returned as xml
            var e = executor.get_xml().documentElement;
            var empty = e.getAttribute('empty');
    
            if (!empty) 
            {
                //  extract the attributes I am interested in
                var percent = e.getAttribute('progress');
                var file = e.getAttribute('file');
                var kbs = Math.round(parseInt(e.getAttribute('bytes')) / 1024);
                var size = Math.round(parseInt(e.getAttribute('size')) / 1024);
    
                //  update the progressbar to the new value
                progressBar.set_percentage(percent);
    
                //  upadte the message
                updateMessage('info', 'Uploading ' + file + ' ... ' + kbs + ' of ' + size + ' KB');
    
                if (percent == 100)
                {
                    //  clear the interval
                    window.clearInterval(intervalID);
                }
            }
        });
    
        //  invoke the request
        request.invoke();
    
    }, 1000);
    

    【讨论】:

      【解决方案5】:

      您可以从服务器端检查 WebTimeout 异常,然后使用 SignalR 主动将超时消息发送回客户端:

      var request = (HttpWebRequest)WebRequest.Create("http://www.your_third_parties_page.com");
      request.Timeout = 1000; //Timeout after 1000 ms
      
      try
      {
          using (var stream = request.GetResponse().GetResponseStream())
          using (var reader = new StreamReader(stream))
          {
               Console.WriteLine(reader.ReadToEnd());
          }
      }
      catch (WebException ex)
      {
          //Check for timeout exception
          if (ex.Status == WebExceptionStatus.Timeout)
          {
               //If timeout then send SignalR ajax message to inform the clients...
               var context = GlobalHost.ConnectionManager.GetHubContext<YourHub>();
               context.Clients.All.addNewMessageToPage("This method has been processing too long!");
          }
      }
      

      查看更多how to setup SignalR for asp.net here

      【讨论】:

        猜你喜欢
        • 2014-12-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-18
        • 2014-06-05
        • 2013-08-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多