У меня есть WCF-совместимая служба, которая генерирует PDF-документ с помощью сторонней библиотеки. Данные в формате pdf возвращаются в виде массива byte [] и никогда не сохраняются на диске. Пример интерфейса выглядит так:
[OperationContract]
int PdfAnswerSheets(
out byte[] PdfData,
out int byteLength,
int quizId,
string footerLine1,
string footerLine2,
string footerLine3,
string tableHeaderLine,
string quizTitle,
string contentLicensedToName,
string locale,
bool withCues ,
string authId);
Служба работает нормально и возвращает данные, как и ожидалось.
Далее у меня есть PHP, который функционирует как клиент службы, описанной выше. Основная идея заключается в том, что PHP делает запрос в службу, а затем отправляет данные PDF в браузер клиента. как загрузка. Цель (очень преднамеренно) состоит в том, чтобы заставить все это работать динамически и, например, без необходимости сохранять копию PDF на диск и ссылаться на нее по временному URL и т. Д.
Вот так выглядит PHP
Функция, которая использует сервис WCF. Кажется, это работает нормально.
// Get answer sheets (web service call)
function wcfAS($quizId, $footerLine1, $footerLine2, $footerLine3,
$tableHeaderLine, $quizTitle, $contentLicensedToName,
$locale, $withCues, $authId)
{
$client = new SoapClient('http://192.168.241.91:8080/QuizSheetsSvc.svc?wsdl');
$obj->quizId = $quizId;
$obj->footerLine1 = $footerLine1;
$obj->footerLine2 = $footerLine2;
$obj->footerLine3 = $footerLine3;
$obj->tableHeaderLine = $tableHeaderLine;
$obj->quizTitle = $quizTitle;
$obj->contentLicensedToName = $contentLicensedToName;
$obj->locale = $locale;
//$obj->modePreview = false;
$obj->withCues = $withCues;
$obj->authId = $authId;
$retval = $client->PdfAnswerSheets($obj);
return $retval;
}
Функция, которая запускает загрузку.
function AsDownload($quizId, $footerLine1, $footerLine2, $footerLine3,
$tableHeaderLine, $quizTitle, $contentLicensedToName,
$locale, $withCues, $authId)
{
// Get the response
$svcResponse = wcfAs($quizId, $footerLine1, $footerLine2, $footerLine3,
$tableHeaderLine, $quizTitle, $contentLicensedToName,
$locale, $withCues, $authId);
if (!empty($svcResponse->PdfData))
{
EchoPdfDownload($svcResponse->PdfData, 'AnswerSheets-'.$quizId.'.pdf', $svcResponse->byteLength);
}
}
Функция, которая обрабатывает вывод в браузер
function EchoPdfDownload($data, $fileName, $dataLength)
{
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="'.$fileName.'"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: '.$dataLength);
ob_clean();
echo $data;
}
Проблема, с которой я столкнулся, связана с последней частью, то есть записью двоичных данных в выходной поток и получением загрузки в браузере, которая соответствует данным, предоставленным службой WCF.
—С использованием EchoPdfDownload Реализация выше, я получаю файл загрузки в браузере, который значительно завышен (около 160%) в размере и поврежден. Пахнет как какая-то проблема кодирования или перевода, возникающая по пути
— Я пытался изменить EchoPdfDownload использовать метод PHP unpack () для $ data, прежде чем выводить его. В этом случае я не получаю загрузки вообще
— Я пробовал различные другие методы с небольшим успехом.
Есть идеи?
РЕДАКТИРОВАТЬЯ попытался изменить функцию «Скачать» следующим образом, и вместо того, чтобы получить pdf с завышенным размером, теперь я получаю загрузку в браузере ровно 1 байта:
function EchoPdfDownload($data, $fileName, $dataLength)
{
$binarydata = pack("C*", $data);
$len = count($binarydata);
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="'.$fileName.'"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: '.$len);
ob_clean();
echo $binarydata;
}
РЕДАКТИРОВАТЬ № 2Попытка обойти проблему, вернув код из $ Base64 из Сервиса, расшифровав его на стороне PHP, а затем повторив вывод данных. Это вернуло меня к моей первоначальной проблеме, то есть к раздутой, испорченной загрузке. Скорее всего, проблема связана с тем, как двоичные данные передаются как часть выходного ответа.
function EchoPdfDownload($data, $fileName, $dataLength)
{
// Decode the data from Base64
$decoded = base64_decode($data, true);
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="'.$fileName.'"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: '.$len);
ob_clean();
echo $decoded;
ob_end_flush();
}
Наконец, я должен упомянуть, что AJAX на стороне клиента выглядит следующим образом, на случай, если это будет актуально:
<script>
$(document).ready(function(){
$("#dwnAS").click(function(d){
d.preventDefault();
var ASreqtype = "AS";
var ASfooterLine1 = $("#footerText1").val();
var ASfooterLine2 = $("#footerText2").val();
var ASfooterLine3 = $("#footerText3").val();
var AStableHeaderLine = $("#tableCaption").val();
var ASquizTitle = $("#quizTitle").val();
var ASwithCues = $("#withCues").val();
if (ASquizTitle=='')
{
alert("Please specify a quiz title");
}
else
{
//disable the submission controls
$("#dwnAS").attr("disabled", true);
$("#dwnMS").attr("disabled", true);
$("#dwnTB").attr("disabled", true);
var postData =
'type=' + ASreqtype
+ '&quizId=' + '<?php echo $quizId; ?>'
+ '&footerLine1=' + encodeURIComponent(ASfooterLine1)
+ '&footerLine2=' + encodeURIComponent(ASfooterLine2)
+ '&footerLine3=' + encodeURIComponent(ASfooterLine3)
+ '&tableHeaderLine=' + encodeURIComponent(AStableHeaderLine)
+ '&quizTitle=' + encodeURIComponent(ASquizTitle)
+ '&contentLicensedToName=' + '<?php echo urlencode($contentLicensedToName) ; ?>'
+ '&locale=' + '<?php echo $locale; ?>'
+ '&withCues=' + ASwithCues
+ '&authId=' + '<?php echo urlencode($authId); ?>' ;
// AJAX Code To Submit Form.
$.ajax({
type: "POST",
url: "subscriber-download-post.php",
data: postData,
cache: false,
success: function(response, status, xhr){
$("#dwnAS").attr("disabled", false);
$("#dwnMS").attr("disabled", false);
$("#dwnTB").attr("disabled", false);
downloadresponse(response, status, xhr);
},
error: function(response){
$("#dwnAS").attr("disabled", false);
$("#dwnMS").attr("disabled", false);
$("#dwnTB").attr("disabled", false);
alert("The download failed. Please try again later.");
}
});
}
}
)
}) //$(document).ready(function(){
function downloadresponse(response, status, xhr) {
// check for a filename
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
var type = xhr.getResponseHeader('Content-Type');
var blob = new Blob([response], { type: type });
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location = downloadUrl;
}
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
}
Оказалось, что это вовсе не проблема PHP, а проблема получения двоичных данных с помощью вызова jQuery «ajax». Если у вас есть необходимость получать двоичные данные через ajax-вызов, вы должны использовать двоичный транспорт.
Подробнее по этой ссылке:
http://www.codeproject.com/Questions/879517/Encoding-AJAX-binary-response?arn=1
Других решений пока нет …