[javascript] JSON 데이터로 POST를 통해 파일을 다운로드하는 JavaScript / jQuery

jquery 기반 단일 페이지 웹 응용 프로그램이 있습니다. AJAX 호출을 통해 RESTful 웹 서비스와 통신합니다.

다음을 달성하려고합니다.

  1. JSON 데이터가 포함 된 POST를 REST URL에 제출하십시오.
  2. 요청이 JSON 응답을 지정하면 JSON이 리턴됩니다.
  3. 요청이 PDF / XLS / etc 응답을 지정하면 다운로드 가능한 바이너리가 반환됩니다.

1 & 2가 작동하고 있으며 클라이언트 jquery 앱은 JSON 데이터를 기반으로 DOM 요소를 만들어 웹 페이지에 반환 된 데이터를 표시합니다. 또한 웹 서비스 관점에서 # 3 작업을하고 있습니다. 즉 올바른 JSON 매개 변수가 제공되면 이진 파일을 만들고 반환합니다. 그러나 클라이언트 자바 스크립트 코드에서 # 3을 처리하는 가장 좋은 방법은 확실하지 않습니다.

이처럼 아약스 호출에서 다운로드 가능한 파일을 다시 가져올 수 있습니까? 브라우저가 파일을 다운로드하고 저장하도록하려면 어떻게합니까?

$.ajax({
    type: "POST",
    url: "/services/test",
    contentType: "application/json",
    data: JSON.stringify({category: 42, sort: 3, type: "pdf"}),
    dataType: "json",
    success: function(json, status){
        if (status != "success") {
            log("Error loading data");
            return;
        }
        log("Data loaded!");
    },
    error: function(result, status, err) {
        log("Error loading data");
        return;
    }
});

서버는 다음 헤더로 응답합니다.

Content-Disposition:attachment; filename=export-1282022272283.pdf
Content-Length:5120
Content-Type:application/pdf
Server:Jetty(6.1.11)

또 다른 아이디어는 PDF를 생성하여 서버에 저장하고 URL을 포함하는 JSON을 파일에 반환하는 것입니다. 그런 다음 ajax 성공 핸들러에서 다른 호출을 실행하여 다음과 같은 작업을 수행하십시오.

success: function(json,status) {
    window.location.href = json.url;
}

그러나 그렇게하면 서버를 두 번 이상 호출해야하며, 서버는 다운로드 가능한 파일을 빌드하고 어딘가에 저장 한 다음 주기적으로 해당 스토리지 영역을 정리해야합니다.

이를 달성하는 더 간단한 방법이 있어야합니다. 아이디어?


편집 : $ .ajax에 대한 문서를 검토 한 후 응답 dataType이 하나만 될 수 있다는 것을 xml, html, script, json, jsonp, text알았습니다. 따라서 2 진 파일을 @VinayC 답변에 제안 된 데이터 URI 체계 (원하는 것이 아닙니다).

그래서 내 옵션은 다음과 같습니다.

  1. 아약스를 사용하지 않고 양식 게시물을 제출하고 JSON 데이터를 양식 값에 포함하십시오. 숨겨진 iframe 등을 어지럽 힐 필요가 있습니다.

  2. 표준 GET 요청을 작성하고 window.location.href를이 URL로 설정하기 위해 ajax를 사용하지 말고 JSON 데이터를 쿼리 문자열로 변환하십시오. 브라우저가 응용 프로그램 URL에서 변경되지 않도록 클릭 핸들러에서 event.preventDefault ()를 사용해야 할 수도 있습니다.

  3. 위의 다른 아이디어를 사용하지만 @naikus 답변의 제안으로 향상되었습니다. 웹 서비스가 이것이 아약스 호출을 통해 호출되고 있음을 알리는 매개 변수와 함께 AJAX 요청을 제출하십시오. 웹 서비스가 ajax 호출에서 호출 된 경우 생성 된 자원에 대한 URL이있는 JSON을 리턴하십시오. 자원이 직접 호출되면 실제 2 진 파일을 리턴하십시오.

내가 생각할수록 마지막 옵션이 더 마음에 듭니다. 이렇게하면 요청에 대한 정보 (생성 시간, 파일 크기, 오류 메시지 등)를 다시 얻을 수 있으며 다운로드를 시작하기 전에 해당 정보에 대해 조치를 취할 수 있습니다. 단점은 서버의 추가 파일 관리입니다.

이것을 달성하는 다른 방법이 있습니까? 내가 알아야 할 이러한 방법의 장단점이 있습니까?



답변

letronje 의 솔루션은 매우 간단한 페이지에서만 작동합니다. document.body.innerHTML +=본문의 HTML 텍스트를 가져와 iframe HTML을 추가하고 페이지의 innerHTML을 해당 문자열로 설정합니다. 이렇게하면 페이지에 포함 된 모든 이벤트 바인딩이 지워집니다. 요소를 작성하고 appendChild대신 사용하십시오.

$.post('/create_binary_file.php', postData, function(retData) {
  var iframe = document.createElement("iframe");
  iframe.setAttribute("src", retData.url);
  iframe.setAttribute("style", "display: none");
  document.body.appendChild(iframe);
}); 

또는 jQuery를 사용하여

$.post('/create_binary_file.php', postData, function(retData) {
  $("body").append("<iframe src='" + retData.url+ "' style='display: none;' ></iframe>");
}); 

이것이 실제로하는 일 : postData 변수의 데이터를 사용하여 /create_binary_file.php에 게시를 수행합니다. 해당 게시물이 성공적으로 완료되면 페이지 본문에 새 iframe을 추가하십시오. /create_binary_file.php의 응답에는 생성 된 PDF / XLS / etc 파일을 다운로드 할 수있는 URL 인 ‘url’값이 포함된다고 가정합니다. 해당 URL을 참조하는 페이지에 iframe을 추가하면 웹 서버에 적절한 MIME 유형 구성이 있다고 가정하면 브라우저가 파일 다운로드를 사용자에게 홍보합니다.


답변

나는 blob을 사용하는 다른 옵션으로 놀고있었습니다. 텍스트 문서를 다운로드 할 수 있었고 PDF를 다운로드했습니다 (그러나 손상되었습니다).

Blob API를 사용하면 다음을 수행 할 수 있습니다.

$.post(/*...*/,function (result)
{
    var blob=new Blob([result]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="myFileName.txt";
    link.click();

});

IE 10 이상, Chrome 8 이상, FF 4 이상입니다. https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL을 참조 하십시오.

Chrome, Firefox 및 Opera에서만 파일을 다운로드합니다. 이는 앵커 태그의 다운로드 속성을 사용하여 브라우저가 강제로 다운로드하도록합니다.


답변

나는 이런 종류의 오래된 것을 알고 있지만 더 우아한 해결책을 생각해 냈습니다. 나는 똑같은 문제가 있었다. 내가 제안한 솔루션으로 겪고있는 문제는 모두 서버에 파일을 저장해야한다는 것이었지만 다른 문제가 발생했기 때문에 서버에 파일을 저장하고 싶지 않았습니다 (보안 : 파일에 액세스 할 수 있음) 인증되지 않은 사용자, 정리 : 파일을 제거하는 방법과시기). 그리고 당신처럼, 내 데이터는 폼에 넣기가 어려운 복잡한 중첩 JSON 객체였습니다.

내가 한 것은 두 가지 서버 기능을 만드는 것이 었습니다. 첫 번째는 데이터의 유효성을 검사했습니다. 오류가 있으면 반환됩니다. 오류가 아니라면 base64 문자열로 직렬화 / 인코딩 된 모든 매개 변수를 반환했습니다. 그런 다음 클라이언트에는 숨겨진 입력이 하나만 있고 두 번째 서버 기능에 게시되는 양식이 있습니다. 숨겨진 입력을 base64 문자열로 설정하고 형식을 제출합니다. 두 번째 서버 기능은 매개 변수를 해독 / 역 직렬화하고 파일을 생성합니다. 양식이 페이지의 새 창 또는 iframe에 제출 될 수 있으며 파일이 열립니다.

조금 더 많은 작업이 필요하고 약간 더 많은 처리가 필요하지만 전반적 으로이 솔루션으로 훨씬 나아졌습니다.

코드는 C # / MVC에 있습니다.

    public JsonResult Validate(int reportId, string format, ReportParamModel[] parameters)
    {
        // TODO: do validation

        if (valid)
        {
            GenerateParams generateParams = new GenerateParams(reportId, format, parameters);

            string data = new EntityBase64Converter<GenerateParams>().ToBase64(generateParams);

            return Json(new { State = "Success", Data = data });
        }

        return Json(new { State = "Error", Data = "Error message" });
    }

    public ActionResult Generate(string data)
    {
        GenerateParams generateParams = new EntityBase64Converter<GenerateParams>().ToEntity(data);

        // TODO: Generate file

        return File(bytes, mimeType);
    }

클라이언트에서

    function generate(reportId, format, parameters)
    {
        var data = {
            reportId: reportId,
            format: format,
            params: params
        };

        $.ajax(
        {
            url: "/Validate",
            type: 'POST',
            data: JSON.stringify(data),
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            success: generateComplete
        });
    }

    function generateComplete(result)
    {
        if (result.State == "Success")
        {
            // this could/should already be set in the HTML
            formGenerate.action = "/Generate";
            formGenerate.target = iframeFile;

            hidData = result.Data;
            formGenerate.submit();
        }
        else
            // TODO: display error messages
    }


답변

더 간단한 방법이 있고 양식을 작성하여 게시하면 리턴 마임 유형이 브라우저에서 열릴 수있는 페이지 인 경우 페이지를 재설정 할 위험이 있지만 CSV의 경우 완벽합니다.

밑줄과 jquery가 필요한 예제

var postData = {
    filename:filename,
    filecontent:filecontent
};
var fakeFormHtmlFragment = "<form style='display: none;' method='POST' action='"+SAVEAS_PHP_MODE_URL+"'>";
_.each(postData, function(postValue, postKey){
    var escapedKey = postKey.replace("\\", "\\\\").replace("'", "\'");
    var escapedValue = postValue.replace("\\", "\\\\").replace("'", "\'");
    fakeFormHtmlFragment += "<input type='hidden' name='"+escapedKey+"' value='"+escapedValue+"'>";
});
fakeFormHtmlFragment += "</form>";
$fakeFormDom = $(fakeFormHtmlFragment);
$("body").append($fakeFormDom);
$fakeFormDom.submit();

html, text 등의 경우 mimetype이 application / octet-stream과 같은지 확인하십시오.

PHP 코드

<?php
/**
 * get HTTP POST variable which is a string ?foo=bar
 * @param string $param
 * @param bool $required
 * @return string
 */
function getHTTPPostString ($param, $required = false) {
    if(!isset($_POST[$param])) {
        if($required) {
            echo "required POST param '$param' missing";
            exit 1;
        } else {
            return "";
        }
    }
    return trim($_POST[$param]);
}

$filename = getHTTPPostString("filename", true);
$filecontent = getHTTPPostString("filecontent", true);

header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$filename\"");
echo $filecontent;


답변

간단히 말해, 더 간단한 방법은 없습니다. PDF 파일을 표시하려면 다른 서버 요청을해야합니다. 그러나 대안이 거의 없지만 완벽하지는 않으며 모든 브라우저에서 작동하지는 않습니다.

  1. 데이터 URI 방식 . 이진 데이터가 작 으면 javascript를 사용하여 URI에서 창 전달 데이터를 열 수 있습니다.
  2. Windows / IE 전용 솔루션은 로컬 파일 시스템에 데이터를 저장하고 열려면 .NET 컨트롤 또는 FileSystemObject를 사용하는 것입니다.

답변

이 질문을한지 오래되었지만 같은 과제가 있었고 솔루션을 공유하고 싶습니다. 다른 답변의 요소를 사용하지만 전체를 찾을 수는 없었습니다. 양식이나 iframe을 사용하지 않지만 post / get 요청 쌍이 필요합니다. 요청간에 파일을 저장하는 대신 게시 데이터를 저장합니다. 간단하고 효과적입니다.

고객

var apples = new Array();
// construct data - replace with your own
$.ajax({
   type: "POST",
   url: '/Home/Download',
   data: JSON.stringify(apples),
   contentType: "application/json",
   dataType: "text",

   success: function (data) {
      var url = '/Home/Download?id=' + data;
      window.location = url;
   });
});

섬기는 사람

[HttpPost]
// called first
public ActionResult Download(Apple[] apples)
{
   string json = new JavaScriptSerializer().Serialize(apples);
   string id = Guid.NewGuid().ToString();
   string path = Server.MapPath(string.Format("~/temp/{0}.json", id));
   System.IO.File.WriteAllText(path, json);

   return Content(id);
}

// called next
public ActionResult Download(string id)
{
   string path = Server.MapPath(string.Format("~/temp/{0}.json", id));
   string json = System.IO.File.ReadAllText(path);
   System.IO.File.Delete(path);
   Apple[] apples = new JavaScriptSerializer().Deserialize<Apple[]>(json);

   // work with apples to build your file in memory
   byte[] file = createPdf(apples);

   Response.AddHeader("Content-Disposition", "attachment; filename=juicy.pdf");
   return File(file, "application/pdf");
}


답변

$scope.downloadSearchAsCSV = function(httpOptions) {
  var httpOptions = _.extend({
    method: 'POST',
    url:    '',
    data:   null
  }, httpOptions);
  $http(httpOptions).then(function(response) {
    if( response.status >= 400 ) {
      alert(response.status + " - Server Error \nUnable to download CSV from POST\n" + JSON.stringify(httpOptions.data));
    } else {
      $scope.downloadResponseAsCSVFile(response)
    }
  })
};
/**
 * @source: https://github.com/asafdav/ng-csv/blob/master/src/ng-csv/directives/ng-csv.js
 * @param response
 */
$scope.downloadResponseAsCSVFile = function(response) {
  var charset = "utf-8";
  var filename = "search_results.csv";
  var blob = new Blob([response.data], {
    type: "text/csv;charset="+ charset + ";"
  });

  if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(blob, filename); // @untested
  } else {
    var downloadContainer = angular.element('<div data-tap-disabled="true"><a></a></div>');
    var downloadLink      = angular.element(downloadContainer.children()[0]);
    downloadLink.attr('href', window.URL.createObjectURL(blob));
    downloadLink.attr('download', "search_results.csv");
    downloadLink.attr('target', '_blank');

    $document.find('body').append(downloadContainer);

    $timeout(function() {
      downloadLink[0].click();
      downloadLink.remove();
    }, null);
  }

  //// Gets blocked by Chrome popup-blocker
  //var csv_window = window.open("","","");
  //csv_window.document.write('<meta name="content-type" content="text/csv">');
  //csv_window.document.write('<meta name="content-disposition" content="attachment;  filename=data.csv">  ');
  //csv_window.document.write(response.data);
};