Home / Brudnopis / Asynchroniczny upload plików

Asynchroniczny upload plików 01.01.2010

tagi: php Java flash javascript

Asynchroniczne żądania są elementem, który upodabnia witryny www do aplikacji desktopowych, dzięki nim użytkownik ma wrażenie większej interaktywności. Jednakże, ze względów bezpieczeństwa, nie każdy rodzaj danych można przesyłać poprzez asynchroniczne wywołanie ajaxowe, przykładem takich danych są pliki. To w jaki sposób asynchronicznie (bez przeładowania strony) wysłać plik na serwer www? Jest na to sporo sposobów. Trzy z nich które omówię to: technika "ukrytej ramki" (javascript), wykorzystanie flasha wraz z javascript (swfupload), wykorzystanie apletu javy.


Technika ukrytej ramki


Sposób ten polega na wysyłaniu pliku nie poprzez główne okno przeglądarki, a ukrytą ramkę. Po zakończeniu uploadu ukryta ramka może dać odpowiedź, że transfer się zakończył. Metoda ta jest najbardziej popularna, jednakże jest też najbardziej prymitywna. Nie możemy obsłużyć błędów transferu pliku, jedyna możliwość to odesłanie przez przeglądarkę informacji o pomyślnym wysłaniu pliku lub komunikacie błędu walidacji. Jeśli podczas transferu wydarzy się jakiś inny błąd, np. połączenie z serwerem zostanie zerwane, nie możemy na to zareagować.

Implementacja
Schemat algorytmu tej techniki:
1. Formularz z danymi jest wysyłanie nie do głównego okna przeglądarki, a do ukrytej ramki (ustawić atrybut "target")
2. Żądanie z ukrytej ramki jest obsługiwane przez skrypt php
3. Generowany jest kod html, w którym w zależności czy upload się powiódł, czy nie wykonywana jest odpowiednia funkcja javascript ramki nadrzędnej.
4. Użytkownik widzi rezultat swojej akcji po skutkach funkcji js wywołanej przez ukrytą ramkę.

Formularz html (plik index.html):
[HTML]
  1. <html>
  2. <head>
  3. <script type="text/javascript">
  4. //funkcja zwrotna wywoływana jeśli upload zakończy się sukcesem
  5. function uploadSuccess()
  6. {
  7. alert("Upload zakończył się sukcesem");
  8. }
  9.  
  10. //funkcja zwrotna wywoływana jeśli upload zakończy się błędem
  11. function uploadError()
  12. {
  13. alert("Upload zakończył się błędem");
  14. }
  15. </script>
  16. </head>
  17. <body>
  18. <form action="upload.php" id="form" target="hiddenFrame" enctype="multipart/form-data" method="post">
  19. <input type="file" name="file" />
  20. <input type="submit" value="wyślij" />
  21. </form>
  22.  
  23. <!-- ukryta ramka, przez którą idzie żądanie post -->
  24. <iframe width="0" height="0" frameborder="0" name="hiddenFrame" id="hiddenFrame"></iframe>
  25.  
  26. </body>
  27. </html>


Obsługa uploadu po stronie serwera (plik upload.php)
[PHP]
  1. <?php
  2. $valid = true;
  3. if($_SERVER['REQUEST_METHOD'] != 'POST')
  4. {
  5. $valid = false;
  6. }
  7. else
  8. {
  9. $file = $_FILES['file'];
  10. if(is_uploaded_file($file['tmp_name']))
  11. {
  12. if(!move_uploaded_file($file['tmp_name'], './uploads/'.$file['name']))
  13. {
  14. $valid = false;
  15. }
  16. }
  17. else
  18. {
  19. $valid = false;
  20. }
  21. }
  22. ?>
  23. <html>
  24. <head>
  25. <script type="text/javascript">
  26. window.onload = function()
  27. {
  28. //odpowied? w postaci wywo?ania funkcji ramki nadrz?dnej
  29. <?php if($valid): ?>
  30. parent.uploadSuccess();
  31. <?php else: ?>
  32. parent.uploadError();
  33. <?php endif; ?>
  34. };
  35. </script>
  36. </head>
  37. <body>
  38. </body>
  39. </html>


Istnieją darmowe biblioteki, które realizują asynchroniczne wysyłanie plików poprzez ukrytą ramkę, przykładowo Ajax upload, czy plugin(y) do jQuery (np. ten).


Wykorzystanie flasha + javascript (swfupload)


Niestety, delikatnie mówiąc, nie jestem zbyt mocny w AS, więc upload z wykorzystaniem flasha zaprezentuje na przykładzie znanej biblioteki - swfupload.

Pobieramy najnowszą stabilną wersję. Swfupload ma mnóstwo konfigurowalnych opcji, dzięki tej bibliotece w prosty sposób można zrobić progress bar, kolejnowanie wysyłania plików, wyświetlanie prędkości uploadu i inne ciekawe efekty. Ja zajmę się najprostszym wykorzystaniem tej biblioteki.

Implementacja
Plik index.html
[HTML]
  1. <html>
  2. <head>
  3. <script type="text/javascript" src="layout/swfupload.js"></script>
  4. <script type="text/javascript">
  5. window.onload = function(){
  6. var swf = new SWFUpload({
  7. upload_url: "upload.php",
  8. flash_url: "layout/swfupload.swf",
  9. file_post_name: "file",
  10.  
  11. button_width: "100px",
  12. button_height: "30px",
  13. button_text: "<span>prześlij plik</span>",
  14. button_placeholder_id: "swfupload",
  15. button_action: SWFUpload.BUTTON_ACTION.SELECT_FILE,
  16.  
  17. file_queue_limit: 1,
  18.  
  19. //ustawienie funkcji zwrotnych
  20.  
  21. //wywoływana gdy upload się rozpoczyna
  22. upload_start_handler: function(file){
  23. document.getElementById("files").innerHTML = "";
  24. return true;
  25. },
  26.  
  27. //wywoływana gdy upload zakończy się sukcesem
  28. upload_success_handler: function(file){
  29. document.getElementById("files").innerHTML = "Uploadowano "+file.name;
  30. },
  31.  
  32. //wywoływana gdy upload zakońćzy się błędem
  33. upload_error_handler: function(){
  34. document.getElementById("files").innerHTML = "Błąd podczas wysyłania pliku "+file.name;
  35. },
  36.  
  37. //wywoływana gdy zostanie wybrany plik z okna dialogowego
  38. file_dialog_complete_handler: function(){
  39. swf.startUpload();
  40. }
  41. });
  42. }
  43. </script>
  44. </head>
  45. <body>
  46. <div id="swfupload"></div>
  47. <div id="files"></div>
  48. </body>
  49. </html>


Plik upload.php może być ten sam co w poprzednim przykładzie, jedynie dobrze usunąć kod html. Więcej bardziej zaawansowanych przykładów można znaleźć na stronie domowej projektu.


Wykorzystanie appletu Java


Istnieje biblioteka będącem appletem Javy, która służy do wysyłania plików na serwer, nazywa się ona... jupload. Jednakże nie będe jej wykorzystywał w moim przykładzie. Napiszę bardzo prymitywny applet.

Implementacja
Najpierw zastanówmy się w jaki sposób ma dokonywać się ten upload:
1. Użytkownik wchodzi na stronę www, w jego przeglądarce uruchamia się applet (musi on być podpisany cyfrowo, aby miał odpowiednie uprawnienia dostępu do systemu plikowego)
2. Użytkownik naciska przycisk "wybierz plik", pokazuje mu się swingowe okno dialogowe wyboru pliku, wybiera któryś z plików ze swojego dysku twardego
3. Applet nawiązuje połączenie ze zdalnym serwerem (może być ten sam serwer, na którym applet się znajduje)
4. Applet przygotowywuje i wysyła żądanie POST do zdalnego serwera, wcześniej dołączając do ciała żądania wcześniej wybrany przez użytkownika plik
5. Aplikacja serwerowa (skrypt php, servlet itp.) otrzymuje żadanie POST, dokonuje zapisu pliku na dysku i odsyła jakiś komunikat (np. 200 w razie sukcesu lub 500 w razie błędu)
6. Applet odczytuje kod odpowiedzi i wyświetla odpowiedni komunikat (przesyłanie powiodło się / nie powiodło się)

Kod będzie bardzo uproszczony, nie będe obsługiwał błędów, nie będzie też całego kodu źródłowego gdyż nie chodzi o szczegóły, a o ideę. Cały kod źródłowy wraz z plikiem *.jar zostanie udostępniony.
[JAVA]
  1. //pkt. 2 - ukazanie okna dialogowego, użytkownik wybiera plik
  2. if(chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION)
  3. {
  4. //utworzenie strumienia wejściowego do przesyłanego pliku
  5. FileInputStream in = new FileInputStream(chooser.getSelectedFile());
  6.  
  7. //pkt. 3 - otwarcie połączenia ze zdalnym serwerem
  8. String path = getCodeBase().toString()+"upload.php";
  9. URL url = new URL(path);
  10. HttpURLConnection con = (HttpURLConnection) url.openConnection();
  11.  
  12. //pkt. 4 - konfiguracja połączenia, ustawienie odpowiednich nagłówków i cała żądania, wysłanie żądania
  13. con.setUseCaches(false);
  14. con.setDefaultUseCaches(false);
  15. con.setRequestMethod("POST");
  16. con.setRequestProperty("Connection", "Keep-Alive");
  17. String boundary = "JakisSeparator";
  18. //przesyłamy plik, więc trzeba zmienić typ danych
  19. con.setRequestProperty("Content-Type", "multipart/form-data; boundary="+boundary);
  20. con.setDoOutput(true);
  21. con.setDoInput(true);
  22.  
  23. String endl = "\r\n";
  24. //dołączanie pliku do ciała żądania
  25. DataOutputStream out = new DataOutputStream(con.getOutputStream());
  26. out.writeBytes("--"+boundary+endl);
  27. out.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + URLEncoder.encode(f.getName()) +"\"" + endl);
  28. out.writeBytes("Content-Type: application/octet-stream"+endl);
  29. out.writeBytes(endl);
  30.  
  31. //właściwe dołączanie bajtów pliku
  32. byte[] bytes = new byte[1024];
  33. int read = 0;
  34. while((read = in.read(bytes)) != -1)
  35. {
  36. out.write(bytes, 0, read);
  37. }
  38.  
  39. out.writeBytes(endl+"--"+boundary+"--"+endl);
  40. out.flush();
  41.  
  42. //wysłanie żądania
  43. con.connect();
  44. //pkt. 5 - teraz aplikacja serwerowa otrzymała żądanie i je obsługuje
  45.  
  46. //pkt. 6 - sprawdzenie kodu odpowiedzi, i ustawienie odpowiedniego komunikatu (text to obiekt JLabel)
  47. if(con.getResponseCode() == 200)
  48. text.setText("przesłano plik: "+f.getName());
  49. else
  50. text.setText("błąd podczas przesyłania pliku: "+f.getName());
  51.  
  52. //zamknięcie strumieni, połączenia itp.
  53. }


Kod źródłowy: pobierz

Podsumowanie
Asynchroniczny upload plików jest możliwy na wiele sposobów, omówiłem jedynie 3 najbardziej popularne. W erze gdzie praktycznie każdy ma zainstalowany plugin flasha (a napewno większy odsetek użytkowników niż plugin javy), wykorzystanie swfupload wydaje się dobrym wyborem, zwłaszcza że daje on naprawdę duże możliwości. Jednakże należy pamiętać, że flash jest inaczej obsługiwany przez różne systemy operacyjne (tutaj java góruje :P) i mogą pojawić się problemy. Przesyłanie plików przez ukrytą ramkę jest najbardziej prymitywnym, ale też najbardziej uniwersalnym sposobem - jedynie należy napisać kod javascript, który będzie działał pod każdą popularną przeglądarką (użycie jQuery niemalże to gwarantuje).

Komentarze (0)

Brak komentarzy.

Dodaj komentarz