JavaScript / jQuery do pobrania pliku przez POST z danymi JSON

250

Mam jednostronną aplikację internetową opartą na jquery. Komunikuje się z usługą internetową RESTful za pośrednictwem połączeń AJAX.

Próbuję wykonać następujące czynności:

  1. Prześlij POST zawierający dane JSON do adresu URL REST.
  2. Jeśli żądanie określa odpowiedź JSON, zwracana jest JSON.
  3. Jeśli żądanie określa odpowiedź w formacie PDF / XLS / etc, zwracany jest plik binarny do pobrania.

Mam teraz 1 i 2, a aplikacja kliencka jquery wyświetla zwrócone dane na stronie WWW, tworząc elementy DOM na podstawie danych JSON. Mam również # 3 działający z punktu widzenia usługi sieciowej, co oznacza, że ​​utworzy i zwróci plik binarny, jeśli otrzyma prawidłowe parametry JSON. Ale nie jestem pewien, jak najlepiej poradzić sobie z numerem 3 w kodzie javascript klienta.

Czy można odzyskać plik do pobrania z takiego wywołania ajax? Jak mogę pobrać i zapisać plik w przeglądarce?

$.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;
    }
});

Serwer odpowiada następującymi nagłówkami:

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

Innym pomysłem jest wygenerowanie pliku PDF i zapisanie go na serwerze i zwrócenie JSON, który zawiera adres URL pliku. Następnie wywołaj kolejne wywołanie w module obsługi sukcesu ajax, aby zrobić coś takiego:

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

Ale robienie tego oznacza, że ​​musiałbym wykonać więcej niż jedno połączenie z serwerem, a mój serwer musiałby budować pliki do pobrania, przechowywać je gdzieś, a następnie okresowo czyścić ten obszar pamięci.

Musi istnieć prostszy sposób na osiągnięcie tego. Pomysły?


EDYCJA: Po przejrzeniu dokumentacji dla $ .ajax, widzę, że typ danych odpowiedzi może być tylko jeden xml, html, script, json, jsonp, text, więc zgaduję, że nie ma sposobu na bezpośrednie pobranie pliku za pomocą żądania ajax, chyba że osadzę plik binarny przy użyciu Schemat URI danych, jak sugerowano w odpowiedzi @VinayC (nie jest to coś, co chcę zrobić).

Sądzę więc, że moje opcje to:

  1. Nie używaj ajax, a zamiast tego prześlij formularz formularza i umieść moje dane JSON w wartościach formularza. Prawdopodobnie musiałby zadzierać z ukrytymi ramkami iframe i tak dalej.

  2. Nie używaj ajax i zamiast tego przekonwertuj moje dane JSON na ciąg zapytania, aby zbudować standardowe żądanie GET i ustawić window.location.href na ten adres URL. Może być konieczne użycie event.preventDefault () w moim module obsługi kliknięć, aby przeglądarka nie zmieniła adresu URL aplikacji.

  3. Użyj mojego innego pomysłu powyżej, ale wzbogaconego sugestiami z odpowiedzi @naikus. Prześlij żądanie AJAX z pewnym parametrem, który informuje serwis internetowy o wywołaniu za pośrednictwem wywołania ajax. Jeśli usługa internetowa jest wywoływana z wywołania ajax, po prostu zwróć JSON z adresem URL do wygenerowanego zasobu. Jeśli zasób jest wywoływany bezpośrednio, zwróć rzeczywisty plik binarny.

Im więcej o tym myślę, tym bardziej podoba mi się ostatnia opcja. W ten sposób mogę odzyskać informacje o żądaniu (czas wygenerowania, rozmiar pliku, komunikaty o błędach itp.) I mogę działać na podstawie tych informacji przed rozpoczęciem pobierania. Minusem jest dodatkowe zarządzanie plikami na serwerze.

Jakieś inne sposoby na osiągnięcie tego? Jakieś zalety / wady tych metod, o których powinienem wiedzieć?

Tauren
źródło
możliwy duplikat pobierania pliku uchwytu z ajax postu
IsmailS
6
ten był „trochę” wcześniej, więc jak to jest duplikat
GiM
1
url = 'http://localhost/file.php?file='+ $('input').val(); window.open(url); Prosty sposób na uzyskanie tych samych rezultatów. Umieść nagłówek w pliku php. Nie trzeba wysyłać żadnych
aukcji

Odpowiedzi:

171

Rozwiązanie letronje działa tylko dla bardzo prostych stron. document.body.innerHTML +=pobiera tekst HTML treści, dołącza ramkę HTML iframe i ustawia innerHTML strony na ten ciąg. Spowoduje to usunięcie m.in. powiązań zdarzeń na stronie. Utwórz element i użyj appendChildzamiast niego.

$.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);
}); 

Lub używając jQuery

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

Co to właściwie robi: wykonaj post w pliku /create_binary_file.php z danymi w zmiennej postData; jeśli ten post zakończy się powodzeniem, dodaj nową ramkę iframe do treści strony. Zakłada się, że odpowiedź z /create_binary_file.php będzie zawierać wartość „url”, czyli adres URL, z którego można pobrać wygenerowany plik PDF / XLS / etc. Dodanie ramki iframe do strony, która odwołuje się do tego adresu URL, spowoduje, że przeglądarka zachęci użytkownika do pobrania pliku, przy założeniu, że serwer WWW ma odpowiednią konfigurację typu MIME.

SamStephens
źródło
1
Zgadzam się, jest to lepsze niż używanie +=i odpowiednio zaktualizuję moją aplikację. Dzięki!
Tauren
3
Lubię tę koncepcję, jednak Chrome ma ten komunikat w konsoli: Resource interpreted as Document but transferred with MIME type application/pdf. Następnie ostrzega mnie, że plik może być niebezpieczny. Jeśli odwiedzę bezpośrednio retData.url, nie będzie żadnych ostrzeżeń ani problemów.
Michael Irey,
Patrząc w Internecie, wygląda na to, że możesz mieć problemy z plikami PDF w iFrames, jeśli Twój serwer nie ustawia poprawnie Typu zawartości i Dyspozycji. Właściwie nie korzystałem z tego rozwiązania, po prostu wyjaśniłem poprzednie rozwiązanie, więc obawiam się, że nie mam innych porad.
SamStephens,
1
@Tauren: Jeśli zgadzasz się, że jest to lepsza odpowiedź, pamiętaj, że możesz zmienić zaakceptowaną odpowiedź w dowolnym momencie - a nawet całkowicie usunąć znak akceptacji. Po prostu sprawdź nową odpowiedź, a znak akceptacji zostanie przeniesiony. (Stare pytanie, ale ostatnio
pojawiło
5
jaki powinien być retData.url?
Mark Thien
48

Bawiłem się z inną opcją, która wykorzystuje obiekty BLOB. Udało mi się go pobrać do pobierania dokumentów tekstowych i pobrałem pliki PDF (jednak są one uszkodzone).

Za pomocą interfejsu API obiektów blob można wykonać następujące czynności:

$.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();

});

To jest IE 10+, Chrome 8+, FF 4+. Zobacz https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL

Plik zostanie pobrany tylko w Chrome, Firefox i Opera. Używa atrybutu pobierania tagu zakotwiczenia, aby zmusić przeglądarkę do pobrania.

JoshBerke
źródło
1
Brak obsługi atrybutu pobierania w przeglądarce IE i Safari :( ( caniuse.com/#feat=download ). Możesz otworzyć nowe okno i umieścić tam zawartość, ale nie masz pewności co do pliku PDF lub Excel ...
Marçal Juan
1
Należy zwolnić pamięć przeglądarki po wywołaniu click:window.URL.revokeObjectURL(link.href);
Edward Brey
@JoshBerke To jest stare, ale działa na moim końcu. Jak mogę to zrobić, aby natychmiast otworzyć katalog, aby zapisać, zamiast zapisywać w przeglądarce? Czy istnieje również sposób na określenie nazwy pliku przychodzącego?
Jo Ko
2
Spowoduje to uszkodzenie plików binarnych, ponieważ są one konwertowane na ciągi znaków przy użyciu kodowania, a wynik nie jest bardzo zbliżony do oryginalnego
pliku
2
Może używać mimetypes, aby nie uszkadzać
Mateo Tibaquira
18

Znam tego rodzaju stare, ale myślę, że wymyśliłem bardziej eleganckie rozwiązanie. Miałem dokładnie ten sam problem. Problem, który miałem z sugerowanymi rozwiązaniami, polegał na tym, że wszystkie wymagały zapisania pliku na serwerze, ale nie chciałem zapisywać plików na serwerze, ponieważ wprowadził inne problemy (bezpieczeństwo: dostęp do pliku można było uzyskać przez nieautoryzowani użytkownicy, czyszczenie: jak i kiedy pozbywasz się plików). I podobnie jak ty, moje dane były złożone, zagnieżdżone obiekty JSON, które trudno byłoby umieścić w formie.

Zrobiłem dwie funkcje serwera. Pierwszy zweryfikował dane. Jeśli wystąpił błąd, zostanie on zwrócony. Jeśli to nie był błąd, zwróciłem wszystkie parametry zserializowane / zakodowane jako ciąg base64. Następnie na kliencie mam formularz, który ma tylko jedno ukryte wejście i posty na drugiej funkcji serwera. Ustawiam ukryte dane wejściowe na ciąg base64 i przesyłam format. Druga funkcja serwera dekoduje / deserializuje parametry i generuje plik. Formularz może zostać przesłany do nowego okna lub ramki iframe na stronie, a plik się otworzy.

W grę wchodzi trochę więcej pracy i być może trochę więcej przetwarzania, ale ogólnie z tym rozwiązaniem czułem się znacznie lepiej.

Kod jest w 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);
    }

na kliencie

    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
    }
Amersk
źródło
1
Nie przyjrzałem się dobrze temu rozwiązaniu, ale warto zauważyć, że nic o rozwiązaniu używającym create_binary_file.php nie wymaga zapisywania plików na dysku. Byłoby całkowicie możliwe, aby plik create_binary_file.php generował pliki binarne w pamięci.
SamStephens,
11

Jest prostszy sposób, stwórz formularz i opublikuj go, ryzykuje to zresetowaniem strony, jeśli typ mime powrotu jest czymś, co otworzy przeglądarka, ale dla csv i takie jest idealne

Przykład wymaga podkreślenia i 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();

W przypadku html, tekstu itp. Upewnij się, że typ mimetyczny to coś takiego jak application / octet-stream

kod 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;
aqm
źródło
8

Krótko mówiąc, nie ma prostszego sposobu. Musisz wykonać kolejne żądanie serwera, aby wyświetlić plik PDF. Chociaż istnieje kilka alternatyw, ale nie są one idealne i nie będą działać na wszystkich przeglądarkach:

  1. Spójrz na schemat URI danych . Jeśli dane binarne są małe, możesz użyć javascript, aby otworzyć okno przekazujące dane w URI.
  2. Jedynym rozwiązaniem dla Windows / IE byłoby posiadanie kontroli .NET lub FileSystemObject, aby zapisać dane w lokalnym systemie plików i stamtąd je otworzyć.
VinayC
źródło
Dzięki, nie byłem świadomy schematu URI danych. Wygląda na to, że może to być jedyny sposób na zrobienie tego w jednym żądaniu. Ale tak naprawdę nie chcę iść w tym kierunku.
Tauren
Z powodu wymagań klienta rozwiązałem ten problem, używając nagłówków serwera w response ( Content-Disposition: attachment;filename="file.txt"), ale naprawdę chcę wypróbować to podejście następnym razem. URIWcześniej korzystałem z danych dla miniatur, ale nigdy nie przyszło mi do głowy, aby używać ich do pobierania - dziękuję za udostępnienie tego samorodka.
brichins
8

Minęło trochę czasu, odkąd zadano to pytanie, ale miałem takie samo wyzwanie i chcę podzielić się moim rozwiązaniem. Wykorzystuje elementy z innych odpowiedzi, ale nie byłem w stanie go znaleźć w całości. Nie używa formularza ani ramki iframe, ale wymaga pary żądań post / get. Zamiast zapisywać plik między żądaniami, zapisuje dane wpisu. Wydaje się być zarówno prosty, jak i skuteczny.

klient

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;
   });
});

serwer

[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");
}
Frank Rem
źródło
1
Myślę, że najbardziej podoba mi się to rozwiązanie. W moim przypadku jednak generuję plik, więc myślę, że spróbuję zachować plik w pamięci, aż będzie dostępny, a następnie zwolnię pamięć po pobraniu, więc nigdy nie będę musiał zapisywać na dysku.
BVernon
5
$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);
};
James McGuigan
źródło
3

Nie do końca odpowiedź na oryginalny post, ale szybkie i brudne rozwiązanie do publikowania obiektu json na serwerze i dynamicznego generowania pobierania.

JQuery po stronie klienta:

var download = function(resource, payload) {
     $("#downloadFormPoster").remove();
     $("<div id='downloadFormPoster' style='display: none;'><iframe name='downloadFormPosterIframe'></iframe></div>").appendTo('body');
     $("<form action='" + resource + "' target='downloadFormPosterIframe' method='post'>" +
      "<input type='hidden' name='jsonstring' value='" + JSON.stringify(payload) + "'/>" +
      "</form>")
      .appendTo("#downloadFormPoster")
      .submit();
}

.. a następnie dekodowanie łańcucha json na serwerze i ustawianie nagłówków do pobrania (przykład PHP):

$request = json_decode($_POST['jsonstring']), true);
header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename=export.csv');
header('Pragma: no-cache');
ralftar
źródło
Podoba mi się to rozwiązanie. W pytaniu PO brzmi to tak, jakby osoba żądająca wiedziała, czy spodziewać się pobrania pliku lub danych JSON, więc mógł zdecydować po stronie klienta i opublikować pod innym adresem URL, zamiast decydować po stronie serwera.
Sam
2

Myślę, że najlepszym rozwiązaniem jest użycie kombinacji. Twoje drugie podejście wydaje się być eleganckim rozwiązaniem, w które zaangażowane są przeglądarki.

Tak więc w zależności od sposobu wykonania połączenia. (niezależnie od tego, czy jest to przeglądarka, czy wywołanie usługi internetowej), możesz użyć ich kombinacji, wysyłając adres URL do przeglądarki i wysyłając surowe dane do dowolnego innego klienta usługi internetowej.

naikus
źródło
Moje drugie podejście również wydaje mi się teraz bardziej atrakcyjne. Dziękujemy za potwierdzenie, że jest to wartościowe rozwiązanie. Czy sugerujesz, że przekazuję dodatkową wartość w obiekcie json, która wskazuje, że to żądanie jest wysyłane z przeglądarki jako wywołanie ajax zamiast wywołania usługi WWW? Jest kilka sposobów na osiągnięcie tego, o czym mogę myśleć, ale jakiej techniki byś użył?
Tauren
czy nie może tego określić nagłówek http User-Agent? Lub jakiś inny nagłówek http?
naikus
1

Nie śpię już od dwóch dni, próbując dowiedzieć się, jak pobrać plik za pomocą jquery z wywołaniem ajax. Całe wsparcie, które otrzymałem, nie mogło pomóc w mojej sytuacji, dopóki nie spróbuję tego.

Strona klienta

function exportStaffCSV(t) {
   
    var postData = { checkOne: t };
    $.ajax({
        type: "POST",
        url: "/Admin/Staff/exportStaffAsCSV",
        data: postData,
        success: function (data) {
            SuccessMessage("file download will start in few second..");
            var url = '/Admin/Staff/DownloadCSV?data=' + data;
            window.location = url;
        },
       
        traditional: true,
        error: function (xhr, status, p3, p4) {
            var err = "Error " + " " + status + " " + p3 + " " + p4;
            if (xhr.responseText && xhr.responseText[0] == "{")
                err = JSON.parse(xhr.responseText).Message;
            ErrorMessage(err);
        }
    });

}

Po stronie serwera

 [HttpPost]
    public string exportStaffAsCSV(IEnumerable<string> checkOne)
    {
        StringWriter sw = new StringWriter();
        try
        {
            var data = _db.staffInfoes.Where(t => checkOne.Contains(t.staffID)).ToList();
            sw.WriteLine("\"First Name\",\"Last Name\",\"Other Name\",\"Phone Number\",\"Email Address\",\"Contact Address\",\"Date of Joining\"");
            foreach (var item in data)
            {
                sw.WriteLine(string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\"",
                    item.firstName,
                    item.lastName,
                    item.otherName,
                    item.phone,
                    item.email,
                    item.contact_Address,
                    item.doj
                    ));
            }
        }
        catch (Exception e)
        {

        }
        return sw.ToString();

    }

    //On ajax success request, it will be redirected to this method as a Get verb request with the returned date(string)
    public FileContentResult DownloadCSV(string data)
    {
        return File(new System.Text.UTF8Encoding().GetBytes(data), System.Net.Mime.MediaTypeNames.Application.Octet, filename);
        //this method will now return the file for download or open.
    }

Powodzenia.

Otis-iDev
źródło
2
To zadziała, w przypadku małych plików. Ale jeśli masz duży plik, będziesz mieć problem z zbyt długim adresem URL. Wypróbuj go z 1000 rekordów, to złamie i wyeksportuje tylko część danych.
Michael Merrell,
1

Znalazłem go dawno temu i działa idealnie!

let payload = {
  key: "val",
  key2: "val2"
};

let url = "path/to/api.php";
let form = $('<form>', {'method': 'POST', 'action': url}).hide();
$.each(payload, (k, v) => form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v})) );
$('body').append(form);
form.submit();
form.remove();
Den Nikitin
źródło
0

Innym podejściem zamiast zapisywania pliku na serwerze i pobierania go, jest użycie .NET 4.0+ ObjectCache z krótkim terminem ważności do drugiej akcji (w tym czasie można go ostatecznie zrzucić). Powodem, dla którego chcę użyć JQuery Ajax do wykonania połączenia, jest to, że jest on asynchroniczny. Zbudowanie mojego dynamicznego pliku PDF zajmuje sporo czasu i w tym czasie wyświetlam okno dialogowe zajętego pokrętła (pozwala również na wykonanie innej pracy). Podejście polegające na wykorzystaniu danych zwróconych w „sukcesie” do utworzenia obiektu Blob nie działa niezawodnie. To zależy od zawartości pliku PDF. Łatwo jest go zepsuć dane w odpowiedzi, jeśli nie jest ono całkowicie tekstowe, a to wszystko, co Ajax może obsłużyć.

Wray Smallwood
źródło
0

Rozwiązanie

Wydaje mi się, że załącznik Content-Disposition działa:

self.set_header("Content-Type", "application/json")
self.set_header("Content-Disposition", 'attachment; filename=learned_data.json')

Obejście

application / octet-stream

Z JSONem działo się coś podobnego, dla mnie po stronie serwera ustawiałem nagłówek na self.set_header („Content-Type”, „ application / json ”), ale kiedy zmieniłem go na:

self.set_header("Content-Type", "application/octet-stream")

Automatycznie go pobrał.

Wiedz również, że aby plik zachował sufiks .json, musisz to zrobić w nagłówku nazwy pliku:

self.set_header("Content-Disposition", 'filename=learned_data.json')
Mr-Programs
źródło
-1

Dzięki HTML5 możesz po prostu utworzyć kotwicę i kliknąć ją. Nie ma potrzeby dodawania go do dokumentu jako dziecko.

const a = document.createElement('a');
a.download = '';
a.href = urlForPdfFile;
a.click();

Wszystko gotowe.

Jeśli chcesz mieć specjalną nazwę do pobrania, przekaż ją w downloadatrybucie:

const a = document.createElement('a');
a.download = 'my-special-name.pdf';
a.href = urlForPdfFile;
a.click();
przepisane
źródło
4
pytanie dotyczy metody POST
dovid