W poprzedniej części umieściliśmy plik na serwerze za pomocą API, dzisiaj pozwolimy użytkownikom naszej aplikacji wyświetlać / pobierać te pliki.
Uwaga: opisane tutaj podejście nie jest optymalne i sprawdzi się tylko w przypadku małych aplikacji lub aplikacji, w których wymagana jest kontrola dostepu do tych plików. W przypadku realnych aplikacji, które mają obsługiwać sporo użytkowników, poczytaj o dobrych praktykach w dalszej części tej lekcji.
Lekcja
Jeśli zrealizowałaś poprzednią lekcję nie będziemy potrzebowali żadnych dodatkowych bibliotek, wystarczą te, które popraliśmy poprzednio.
Postępujemy analogicznie jak w poprzednim przypadku – prześledźmy najpierw kod kontrolera, który pozwala na pobranie jakiegoś pliku:
@RequestMapping(value = "/plik/{nazwa_pliku}", method = RequestMethod.GET)
public void getFile(@PathVariable("nazwa_pliku") String nazwaPliku, HttpServletResponse response) {
try {
InputStream is = new FileInputStream(nazwaPliku);
FileMetadata metedataObject = ...;//tutaj pobieramy dane z bazy danych
response.setContentType(metadataObject.getContentType());
response.setContentLength(metadataObject.getSize());
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
log.info("Błąd przy pobieraniu pliku", ex);
response.setStatus(404);
}
}
Kontroler ten wykorzystuje klasę IOUtils z pakietu apache commons IO, metoda copy przepisuje strumień wejściowy na strumień wyjściowy. Dzięki temu unikamy samodzielnego pisania pętli.
Weźmy też przykład z dokumentacji S3 :
AmazonS3 s3Client = new AmazonS3Client(new ProfileCredentialsProvider());
S3Object object = s3Client.getObject(
new GetObjectRequest(bucketName, key));
InputStream objectData = object.getObjectContent();
Po połaczeniu obu fragmentów otrzymujemy gotowe do użycia rozwiązanie (zwróć uwagę, że zamieniliśmy też sposób przekazywania danych dostępowych, podobnie jak w poprzedniej części tej lekcji):
@RequestMapping(value = "/plik/{nazwa_pliku}", method = RequestMethod.GET)
public void getFile(@PathVariable("nazwa_pliku") String nazwaPliku, HttpServletResponse response) {
try {
AmazonS3 s3client = new AmazonS3Client(new BasicAWSCredentials(accessKey, secretKey));
S3Object object = s3Client.getObject(new GetObjectRequest(bucketName, key));
InputStream is = object.getObjectContent();
FileMetadata metedataObject = ...;//tutaj pobieramy dane z bazy danych
response.setContentType(metadataObject.getContentType());
response.setContentLength(metadataObject.getSize());
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
log.info("Błąd przy pobieraniu pliku", ex);
response.setStatus(404);
}
}
Zwróć uwagę, jak przydatne jest w tym wypadku posiadanie metadanych pliku w bazie danych – dzięki temu możemy te informacje od razu uzupełnić i wysłać do użytkownika, w innym wypadku musielibyśmy je obliczać lub generować w jakiś sposób, co nie byłoby takie proste.
Po wpisaniu w przeglądarkę adresu http://nasza.aplikacja.pl:8080/plik/przyklad.jpg powinniśmy otrzymać nasz plik (oczywiście pod warunkiem, że go tam wcześniej umieściliśmy). Kod ten ma podstawową obsługę wyjatków – łapie wszystkie wyjątki i zwraca wtedy kod http 404 oznaczający, że plik nie istnieje. W rzeczywistości każdy rodzaj wyjątku powinien być obsługiwany oddzielnie wraz z zapisywaniem informacji do loggera.
Tak obsługiwany adres możemy wykorzystać zarówno jako link do pliku (tag HTML a) jak i jako źródło obrazka (w HTMLu <img src=”…” />
Dobre praktyki
W praktyce dostęp do plików, które nie są tajne (np. Zdjęcia profilowe, zdjęcia wgrane przez użytkownika itp) udostępnia się w inny sposób – poprzez tzw. CDN. CDN to skrót od Angielskiego wyrażenia Content Delivery Network i jest to podejście do udostępniania treści statycznych (jak zdjęcia) dużej liczbie użytkowników (czasami także w zależności od regionu świata, z którego pochodzą). Powodem takiego podejścia jest to, że serwer aplikacji, aby odczytać taki plik a następnie odesłać go do użytkownika, musi wykonać sporo operacji. Jeśli mamy bardzo dużo użytkowników, generuje to realny, niemały koszt. Dlatego stosuje się serwery, które potrafią tylko i wyłącznie wyświetlać treści statyczne – dzięki usunięciu wszystkich innych funkcji są dużo szybsze od standardowych serwerów, a więc koszt przez nie generowany jest niższy.
W przypadku niektórych usług przechowywania plików istnieją dedykowane rozwiązania – np. Dla usługi S3 istnieje usługa CloudFront, która m.in. może działać jako CDN dla plików przechowywanych w usłudze S3.
Temat ten nie będzie rozwinięty w tej lekcji ponieważ dotyczy on problemów, na które napotykamy w przypadku większych stron (z odsłonami liczonymi w milionach). Wtedy też najczęściej zatrudniamy po prostu osobę która ma w tym doświadczenie zamiast robić to samemu ;) Ponadto dochodzi wtedy wiele kwestii szczegółowej konfiguracji, dostosowanej pod konkretną aplikację, aby maksymalnie zwiększyć jej wydajność.
Podsumowanie
Teraz wiesz już, jak możesz zarówno przesyłać pliki od użytkowników za pomocą API jak i umożliwiać ich pobranie. Zachęcam do przetestowania tej metody z innymi publicznie dostępnymi API – np. Do pobierania pogody (wheather.yahoo.com) czy też zwracającymi współrzędne geograficzne adresu (Google Maps API). Możliwości są nieograniczone, a umiejętność korzystania z zewnętrznego API będzie Ci potrzebna nieraz.
Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!