Goście wysyłają zdjęcia i filmy z dowolnego telefonu, a pliki zostają na własnym serwerze klienta

Goście weselni skanują kod QR, otwierają jeden sekretny adres i wysyłają zdjęcia oraz filmy prosto z telefonu: bez aplikacji, bez konta, bez logowania. Pliki do 5 GB lądują na dysku klienta, a para młoda ogląda galerię, która zapełnia się jeszcze w trakcie przyjęcia. Jeden wieczór, bez drugiej szansy, i telefony sprzed sześciu lat.

  • Pliki do 5 GB, z wznawianiem
  • Przeglądarki sprzed sześciu lat, testowane na prawdziwych silnikach
  • Wszystko na własnym serwerze klienta, stawianym jednym poleceniem Ansible
Telefon z kodem QR, plik podzielony na porcje, z których ostatnia jest pomarańczowa, trzy równoległe strumienie do serwera z własnym dyskiem oraz zapełniająca się siatka galerii z jednym nowym pomarańczowym kafelkiem

Klient prywatny: przyjęcie weselne z gośćmi w wieku od 20 do 80 lat i dowolnymi telefonami, jakie przynieśli

Wyzwanie

Pięć założeń ustawiło cały projekt: do 5 GB na plik, słabe WiFi w telefonach, goście w każdym wieku z przeglądarkami sprzed kilku lat, dane zostające u klienta i jedna noc bez drugiej szansy.

Każde z nich miało konsekwencję techniczną: upload dzielony na porcje z wznawianiem, strumieniowanie na dysk zamiast buforowania, wersja działająca bez JavaScriptu, własny serwer klienta opisany jako kod oraz testy na prawdziwych starych przeglądarkach zamiast w emulacji.

Rozwiązanie

Trzy kontenery za nginx: frontend Next.js w TypeScripcie, silnik wysyłki w czystym TypeScripcie oddzielony od warstwy widoku oraz backend FastAPI, który strumieniuje porcje na dysk, prowadzi metadane i buduje galerię z pomocą ffmpeg. Sekretny token z kodu QR jest częścią każdego adresu i autoryzuje każde żądanie; nie ma kont, sesji ani ciasteczek.

Serwer jest przygotowywany, wdrażany i serwisowany przez Ansible: jedno polecenie, kontrola wstępna przed zmianą, sekrety w zaszyfrowanym magazynie, a wdrożenie uznaje się za udane dopiero, gdy backend, nginx i strona odpowiadają.

Wyniki

Na plik
Do 5 GB, w porcjach po 8 MB ze wznowieniem po zerwaniu połączenia
Równoległe strumienie na wysyłkę
3, z ponawianiem w rosnącym odstępie
Wersje przeglądarek
6 lat wstecz, sprawdzone na prawdziwych silnikach z sumą kontrolną pliku
Usługi zewnętrzne
0: pliki zostają na dysku klienta o pojemności 14 TB
Stawianie i aktualizacja serwera
1 polecenie Ansible, z kontrolą przed zmianą i po niej
Bez sekretnego odnośnika
404 dla strony głównej i każdego złego adresu

Jak pracowaliśmy

  1. Najpierw założenia

    Do 5 GB na plik, słabe WiFi, goście od 20 do 80 lat z dowolnym telefonem, dane u klienta, jeden wieczór. Każde założenie dostało konsekwencję techniczną przed pierwszą linią kodu.

  2. Trzy kontenery, jasne role

    nginx jako jedyny otwarty port i strumieniujące wejście, backend jako jedyny piszący na dysk z danymi, frontend bez dostępu do niego.

  3. Własny silnik wysyłki

    Czysty TypeScript bez frameworka: dzielenie na porcje, równoległość, ponawianie, wznawianie. API to zwykłe HTTP, więc ta sama wysyłka działa z wiersza poleceń, a diagnostyka nie wymaga przeglądarki.

  4. Serwer jako kod

    Ansible przygotowuje serwer, wdraża z kontrolą wstępną i weryfikuje efekt. Kasowanie materiałów testowych wymaga wpisanego wprost potwierdzenia i sprawdza dysk oraz katalog.

  5. Testy na prawdziwych silnikach

    Archiwalne wersje przeglądarek sterowane bezpośrednio, każda pełną ścieżką: otwarcie strony, wysłanie prawdziwego pliku, porównanie sumy kontrolnej, otwarcie galerii.

  6. Siatka bezpieczeństwa bez JavaScriptu

    Jeśli aplikacja nie wystartuje, gość i tak dostaje zwykły formularz HTML, a serwer zapisuje plik dokładnie tak samo jak przy normalnej wysyłce.

Co założenia oznaczały technicznie

Założenie Konsekwencja techniczna
Do 5 GB na plik Upload dzielony na porcje z wznawianiem, strumieniowanie na dysk zamiast buforowania
Telefony, często słabe WiFi Odporność na zerwania, ponowne próby, wznowienie po odświeżeniu strony
Goście od 20 do 80 lat, dowolne telefony Obsługa przeglądarek starszych o kilka lat i wersja działająca bez JavaScriptu
Dane zostają u klienta Własny serwer z dyskiem 14 TB, wdrożenie przez Ansible, zero usług zewnętrznych
Jedna noc, bez drugiej szansy Testy na prawdziwych starych przeglądarkach, nie tylko w emulacji

Wyzwania, które pokonaliśmy

Każdy z poniższych punktów kosztował od kilku godzin do dwóch dni i żadnego nie dało się przewidzieć z dokumentacji.

iPhone po cichu pogarsza to, co wysyła

Film nagrany w 4K trafiał na serwer jako krótki plik 480 na 360, a zdjęcia bywały mniejsze niż oryginały. Nic w aplikacji tego nie robiło. Źródła WebKita mówią jasno: przy wyborze pliku z biblioteki zdjęć Safari na iOS zawsze przekodowuje filmy, pamięta rozmiar wybrany w oknie wyboru na zupełnie innej stronie, a Live Photo oddaje jako jego krótki filmik w niskiej rozdzielczości. Strona internetowa nie ma jak tego wyłączyć. Dlatego aplikacja mówi prawdę: po wyborze pokazuje faktyczną rozdzielczość i długość, ostrzega, gdy widać ślady zmniejszenia, i podsuwa drogę przez aplikację Pliki, która oddaje oryginał. Wymiary każdego pliku zapisujemy na serwerze, więc pytanie, dlaczego film jest słaby, rozstrzyga zapis, a nie domysły.

Zawieszenie na 96 procentach: odpowiedź, która dociera, ale nigdy nie jest widziana

Wysyłki z iPhone'ów dochodziły do 96 do 99 procent i stawały. Logi serwera pokazywały, że wszystko przyjął i zapisał, z czasem odpowiedzi półtorej sekundy. Telefon czekał w nieskończoność. Dwie przyczyny, obie w warstwie sieciowej Apple: przeglądarka gubiła się, gdy ciało żądania było czytane wprost z pliku na dysku, a przy kilku wysyłkach naraz żądanie dostawało kompletną odpowiedź HTTP 200, ale nigdy nie zgłaszało, że się zakończyło. Porcje są teraz najpierw wczytywane do pamięci i wysyłane jako gotowy bufor; jeśli widać pełną, poprawną odpowiedź, porcja liczy się jako dostarczona, a zombie-żądanie jest porzucane; licznik bezczynności, 30 sekund, uruchamia ponowną próbę z rosnącym odstępem. Telefon dosyła też na koniec krótki dziennik zdarzeń, zapisywany przy metadanych pliku, więc zachowanie cudzego telefonu da się później odczytać na serwerze.

Nowoczesny build, stary telefon, biała strona

Na kilkuletnim telefonie z nieaktualizowaną przeglądarką strona wyświetlała się bez stylów, a wysyłka nie działała. Framework buduje kod pod przeglądarki z ostatnich dwóch lat, biblioteka stylów owija wszystko w nowoczesną konstrukcję CSS, którą starszy silnik pomija razem z całym arkuszem, a kilka nowych jednostek i skrótów składniowych zatrzymuje cały skrypt. Do tego dwie pułapki: minifikator CSS usuwał starszą z dwóch wartości zapasowych jako duplikat, a oficjalny mechanizm frameworka do ładowania skryptu „przed całą resztą” ładował go po własnych paczkach. Rozwiązanie: jawna lista obsługiwanych przeglądarek cofnięta o kilka lat, spłaszczenie nowoczesnej konstrukcji przy budowaniu, jednostki zapasowe, łatki na brakujące funkcje i kilka wierszy skryptu wpisanych wprost w nagłówek dokumentu.

Wielki plik kontra domyślne ustawienia serwera

Przy 5 GB na plik większość ustawień domyślnych działa przeciw nam. Serwer pośredniczący buforuje całe żądanie, zanim przekaże je dalej, zapisując gigabajty na własnym dysku tylko po to, żeby zaraz je oddać; limity rozmiaru i czasy oczekiwania są krótkie. Wysyłka idzie teraz strumieniem przez wszystkie warstwy bez pośredniego buforowania, każda porcja trafia na swoje miejsce w rzadkim pliku, zakończenie jest operacją atomową, a powtórzone zakończenie po zerwanej sieci nie tworzy duplikatu. Standardowa biblioteka formularzy w Pythonie dekodowała nazwy plików w starym kodowaniu zachodnioeuropejskim, więc polskie znaki potrafiły wywrócić całe żądanie; zastąpił ją krótki odczyt nagłówka z poprawnym kodowaniem.

Jak testować przeglądarkę sprzed sześciu lat

Nowoczesne narzędzia do testów nie potrafią już sterować przeglądarkami z tamtego okresu. Zamiast zakładać, że powinno działać, pobraliśmy archiwalne wersje silników i sterowaliśmy nimi bezpośrednio protokołem niższego poziomu, każdą pełną ścieżką: otwarcie strony, wysłanie prawdziwego pliku, porównanie sumy kontrolnej z oryginałem, otwarcie galerii i podstron.

Telefon i pasek postępu zatrzymany na ostatnim, pomarańczowym odcinku; serwer oznaczony pomarańczowym krzyżykiem, a pomarańczowa pętla pod paskiem oznacza ponowienie

Co z tego wynika

  • Najdroższe błędy nie są w kodzie, tylko w cudzych ustawieniach domyślnych. Framework budujący pod najnowsze przeglądarki, minifikator usuwający zapas zgodności, proxy buforujące gigabajty, biblioteka czytająca nazwy plików w złym kodowaniu: każdą z tych decyzji podjęto za nas i żadna nie zgłosiła się błędem.
  • „Działa u mnie” to nie jest wynik testu. Połowa problemów ujawniła się wyłącznie na prawdziwym sprzęcie: na konkretnym telefonie, w konkretnej przeglądarce, przy konkretnej sieci. Dlatego testujemy na realnych wersjach, a aplikacja zbiera dowody z urządzeń, do których nie mamy dostępu.
  • Czego nie da się naprawić, to trzeba pokazać. Są ograniczenia, których żadna strona internetowa nie obejdzie. Wtedy zadaniem aplikacji jest powiedzieć użytkownikowi prawdę i podać drogę naokoło.
Nowy telefon, stary telefon z klawiszami i laptop docierają trzema równymi torami do tego samego serwera, oznaczonego trzema haczykami; nowy telefon i laptop idą przez aplikację, stary telefon przez zwykły formularz narysowany na pomarańczowo

Powiązane sytuacje

  • Państwa frontend opiera się na jQuery 1.x lub 2.x i na szablonach Smarty, których nikt nie chce dotykać? Odnawiamy go obszar po obszarze obok działającego systemu i testujemy po każdym kroku. Gałęzi 1.x i 2.x projekt jQuery już nie wspiera (stan: 2026-10).

  • Przenosimy Państwa aplikację w Lucee, ColdFusion lub PHP z klasycznego hostingu do kontenerów. Środowisko można wtedy odtworzyć, zaktualizować i przenieść na inny serwer. Część dostawców hostingu pobiera dopłaty za stare wersje PHP, a dla PHP 8.2 poprawki bezpieczeństwa kończą się 31.12.2026 (stan: 2026-10).

  • Państwa dane są dziś przenoszone ręcznie między dwoma systemami albo partner wymaga interfejsu API? Łączymy istniejące aplikacje przez REST lub SOAP, synchronizujemy dane między nimi i wysyłamy wiadomości transakcyjne oraz powiadomienia z Państwa procesów.

Inne projekty

  • Skala punktów z przerywanym progiem: koperta phishingu przechodzi pomarańczowym łukiem przez próg

    Filtr antyspamowy, który działał, ale nie chronił

    Usługi uruchomione, konfiguracja poprawna, w statusie systemu żadnych błędów. A mimo to phishing lądował w skrzynkach odbiorczych, a newslettery partnerów w folderze spam. Nasi inżynierowie zmierzyli, które mechanizmy wykrywania naprawdę działają, usunęli przyczyny, a trzy miesiące później znaleźli i naprawili regresję, którą było widać dopiero w ujęciu godzinowym.

  • Cztery koperty przechodzą przez bramki SPF, DKIM i DMARC, odhaczone na pomarańczowo, w drodze do skrzynki odbiorczej

    Dostarczalność poczty dla czterech domen firmowych

    Serwer pocztowy czterech domen firmowych niczego nie podpisywał, nie publikował polityki DMARC i był odrzucany przez Microsoft. Nasi inżynierowie sprawdzili go, przetestowali zmianę na jednej domenie i wdrożyli ją partiami. Efekt: wszystkie trzy mechanizmy przechodzą sprawdzenie w Gmailu, blokada Microsoftu została zdjęta, a użytkownicy niczego nie odczuli.

W 20 minut ustalimy, jaki pierwszy krok pasuje do Państwa systemu

Państwo opisują cel i obecny stan, my mówimy otwarcie, czy i jak możemy pomóc. Odpowiedź w ciągu jednego dnia roboczego, bez zobowiązań.

Telefon (niemiecki, polski, angielski) +48 501 250 220
E-mail E-mail

Dostępni: poniedziałek–piątek, 9:00–17:00 (czas środkowoeuropejski); wsparcie i awarie całą dobę (24/7)