Я пытаюсь создать cross domain AJAX форму загрузки и решить несколько вопросов. Я буду использовать Ajax Upload - наиболее удобный ajax загрузчик. Этот загрузчик прекрасно работает на одном домене и очень прост в настройке.
Примечание переводчика: Статья незавершенная, готового решения в ней нет, решил перевести ее потому-что часть кода использованного в ней мне помогло, однако повторюсь, что это не урок.
Вот основы:
Файл клиента:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link href="fileuploader.css" rel="stylesheet" type="text/css"> <style> body {font-size:13px; font-family:arial, sans-serif; width:700px; margin:100px auto;} </style> </head> <body> <p><a href="http://github.com/valums/file-uploader">Вернуться к странице проекта</a></p> <p>Чтобы загрузить файл, нажмите на кнопку ниже. Drag&Drop поддерживается в FF и Chrome.</p> <p>Прогресс-бар поддерживается в FF3.6, Chrome6, Safari4</p> <div id="file-uploader-demo1"> <noscript> <p>Пожалуйста, включите JavaScript, чтобы использовать загрузчик файлов</p> <!-- или можно создать обычную форму загрузки здесь --> </noscript> </div> <script src="fileuploader.js" type="text/javascript"></script> <script> function createUploader(){ var uploader = new qq.FileUploader({ element: document.getElementById('file-uploader-demo1'), action: 'processUpload.php', debug: true }); } // В вашем приложении uploader создается, как только DOM готов(т.е. используем ondomready http://xdan.ru/onDomReady-bez-jQuery.html) // в этом же примере обойдемся полной загрузкой window.onload = createUploader; </script> </body> </html>
Сторона сервера:
require_once('upload.class.php'); // список валидных расширений array("jpeg", "xml", "bmp") $allowedExtensions = array("jpeg", "xml", "bmp"); // максимальный размер файла $sizeLimit = 10 * 1024 * 1024; $uploader = new qqFileUploader($allowedExtensions, $sizeLimit); $result = $uploader->handleUpload('uploads/'); // Для передачи данных через IFRAME вам нужно закодировать все HTML-теги echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);
Это все прекрасно работает, пока вы не измените параметр action в функции createUploader на URL другого домена. Тогда он выходит из строя. При более внимательном рассмотрении с помощью аддона Firefox Live HTTP Headers мы видим, что стандартный запрос пост на одном сервере:
POST processUpload.php?qqfile=5weeks-copy.jpg HTTP/1.1
Host: domain1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
X-Requested-With: XMLHttpRequest
X-File-Name: 5weeks-copy.jpg
Content-Type: application/octet-stream
Referer: http://domain1/test-upload.php
Content-Length: 12154
Origin: http://domain1
Pragma: no-cache
Cache-Control: no-cache
ÿØÿà
запрос меняется при использовании другого домена
OPTIONS http://domain2.com/processUpload.php?qqfile=5weeks-copy.jpg HTTP/1.1
Host: domain2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Origin: http://domain1
Access-Control-Request-Method: POST
Access-Control-Request-Headers: x-file-name,x-requested-with
После поиска для Access-Control-Request-Method и Access-Control-Request-Headers заголовков я наткнулся на статью WC3 Cross-Origin Resource Sharing в которой подробно описывается процесс включения кросс-доменного обмена. После чтения этого документа получается так, как будто браузер запрашивает доступ из внешнего домена, прежде чем пытаться загрузить файл. Таким образом, нам нужен способ, чтобы сообщить браузеру, что это нормально для загрузки на внешний домен. Ну, после долгих испытаний и проб и ошибок, я пришел к этой модифицированной версии моего серверного скрипта загрузки, который позволяет кросс доменную загрузку файла:
//проверки кросс серверного доступа if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && $_SERVER['HTTP_ORIGIN'] == 'http://www.domain1.com'){ header('Access-Control-Allow-Origin: http://www.domain1.com'); header('Access-Control-Allow-Methods: POST'); header('Access-Control-Allow-Headers: X-Requested-With,X-File-Name'); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Max-Age: 1728000'); header("Content-Length: 0"); header("Content-Type: text/plain"); exit(); } else { require_once('upload.class.php'); // Список доступных расширений. array("jpeg", "xml", "bmp") $allowedExtensions = array("jpeg", "xml", "bmp"); // максимальный размер файла $sizeLimit = 10 * 1024 * 1024; $uploader = new qqFileUploader($allowedExtensions, $sizeLimit); $result = $uploader->handleUpload('uploads/'); // Для передачи данных через IFRAME вам нужно закодировать все HTML-теги echo htmlspecialchars(json_encode($result), ENT_NOQUOTES); }
Это проверка для метода OPTIONS с первого домена и отправляет необходимые заголовки, чтобы загрузить файл. Файл загружается прекрасно, но единственная проблема сейчас в том, что по какой-то причине вызов AJAX всегда возвращает ошибку. Это та часть, где я схожу с ума. Ответ для внешнего домена:
HTTP/1.1 200 OK
Date: Thu, 09 Dec 2010 00:27:42 GMT
Server: Apache/2.0.52 (Red Hat)
X-Powered-By: PHP/5.2.14
Vary: Accept-Encoding,User-Agent
Content-Encoding: gzip
Cache-Control: max-age=259200
Expires: Sun, 12 Dec 2010 00:27:42 GMT
Content-Length: 36
Connection: close
Content-Type: text/html
И я проверил, что он возвращает данные, но по какой-то причине нет значения статуса объекта XHR. По глядя на fileuploader.js метод _onComplete вызывается, это означает, что ReadyState возвращает 4 означающую, что вызов AJAX является завершенным:
//строка 1189 of fileupoader.js xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ self._onComplete(id, xhr); } };
Но вызов не завершается должным образом, верный статус не возвращается:
//строка1215 of fileupoader.js if (xhr.status == 200){ this.log("xhr - server response received"); this.log("responseText = " + xhr.responseText); var response; try { response = eval("(" + xhr.responseText + ")"); } catch(err){ response = {}; } this._options.onComplete(id, name, response); } else { this._options.onComplete(id, name, {}); }
Я отлаживал это как сумасшедший, и не мог понять, почему оно не возвращает значение правильно. У кого-нибудь есть какие-либо идеи относительно того, почему это происходит и что я могу попробовать?
оригинал http://www.damnsemicolon.com/php/cross-domain-ajax-upload
Комментарии