added cors

This commit is contained in:
Radek Davidek 2026-03-10 15:47:10 +01:00
parent f36ed55788
commit 22d0e0dfd0
3 changed files with 265 additions and 17 deletions

133
NGINX_SETUP.md Normal file
View File

@ -0,0 +1,133 @@
# Xtream Player - Nginx Setup Guide
## Problem Fixed
Když jste provozovali aplikaci přes nginx, video přehrávač nefungoval. Problém byl v chybějících **CORS (Cross-Origin Resource Sharing)** headerech. Nyní jsme:
1. ✅ Přidali CORS headers do Java backendu
2. ✅ Přidali support pro OPTIONS preflight requests
3. ✅ Vytvořili optimalizovanou nginx konfiguraci pro streamování videa
## Spuštění s Docker Compose (Doporučeno)
Nejjednodušší způsob je spustit obě služby (backend + nginx) najednou:
```bash
docker-compose up -d
```
Aplikace pak bude dostupná na:
- **Frontend s nginx**: http://localhost/
- **Backend přímo**: http://localhost:8080/
## Ruční Spuštění
### 1. Spusťte backend Java aplikaci:
```bash
./mvn clean package
java -jar target/xtream-player-1.0.0.jar
```
### 2. Spusťte nginx:
```bash
docker run -d \
-p 80:80 \
-v $(pwd)/deploy/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro \
nginx:latest
```
Nebo s native nginx (Linux):
```bash
cp deploy/nginx/nginx.conf /etc/nginx/sites-available/xtream-player
sudo ln -s /etc/nginx/sites-available/xtream-player /etc/nginx/sites-enabled/
sudo systemctl reload nginx
```
## HTTPS/SSL Setup
Pro bezpečné HTTPS připojení:
1. Vytvořte SSL certifikáty:
```bash
mkdir -p deploy/nginx/ssl
# Použijte Let's Encrypt, self-signed certs, nebo váš certifikát
```
2. Upravte `deploy/nginx/nginx.conf`:
```nginx
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# ... rest of config
}
# Redirect HTTP to HTTPS
server {
listen 80;
return 301 https://$server_name$request_uri;
}
```
3. Aktualizujte docker-compose.yml pro SSL volume
## Troubleshooting
### Video přehrávač stále nefunguje?
1. **Zkontrolujte browser console** (F12 → Console):
- Hledejte CORS errory
- Hledejte "stream-proxy" errory
2. **Zkontrolujte nginx logs**:
```bash
docker logs <nginx_container_id>
# nebo
sudo tail -f /var/log/nginx/error.log
```
3. **Zkontrolujte backend logs**:
```bash
docker logs <xtream-player_container_id>
# nebo koukejte na console kde jste spustili Java
```
### Nginx vrací 502 Bad Gateway?
- Ujistěte se, že backend je spuštěný na portu 8080
- Zkontrolujte, že nginx má přístup k backendu (network settings)
- Zkontrolujte nginx logs pro konkrétní chybu
### Velké soubory se neloadují?
- V nginx.conf je `client_max_body_size 256M;` - zvyšte v případě potřeby
- Ujistěte se, že `proxy_buffering off;` je nastaveno pro `/api/stream-proxy`
## Architektura
```
Klient (browser)
Nginx (reverse proxy, CORS headers)
Java aplikace (8080)
├─ /api/... (API endpoints s CORS)
├─ /api/stream-proxy (HLS stream proxy)
└─ / (static HTML/CSS/JS)
```
## CORS Headers
Nyní jsou automaticky přidávány všem odpovědím:
```
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
Access-Control-Max-Age: 86400
```
Pokud potřebujete omezit origin (bezpečnější), měňte `Access-Control-Allow-Origin` v kódu.

95
deploy/nginx/nginx.conf Normal file
View File

@ -0,0 +1,95 @@
upstream xtream_backend {
server localhost:8080;
}
server {
listen 80;
server_name _;
# Limit request sizes to prevent abuse
client_max_body_size 256M;
# Root location - serve static files and proxy to backend
location / {
# Try to serve static files first, then proxy to backend
proxy_pass http://xtream_backend;
proxy_http_version 1.1;
# Preserve original request information
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (if needed)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts for long-running requests (streaming)
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 3600s;
# Buffering for streaming
proxy_buffering off;
proxy_request_buffering off;
}
# API endpoints with special handling
location /api/ {
proxy_pass http://xtream_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $server_name;
# For preflight requests
proxy_set_header Access-Control-Allow-Origin *;
# Timeouts for streaming endpoints
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 3600s;
# Disable buffering for stream proxying
proxy_buffering off;
proxy_request_buffering off;
}
# Stream proxy endpoint - special handling for media streams
location /api/stream-proxy {
proxy_pass http://xtream_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Important: disable buffering for media streaming
proxy_buffering off;
proxy_request_buffering off;
# Allow range requests for seeking
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
# Long timeout for streaming
proxy_connect_timeout 60s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
# Pass through the 206 Partial Content status
proxy_pass_request_headers on;
}
# Health check endpoint
location /health {
access_log off;
return 200 "OK";
add_header Content-Type "text/plain";
}
}

View File

@ -80,23 +80,23 @@ public final class XtreamPlayerApplication {
UserAuthenticator userAuthenticator = new UserAuthenticator(applicationDao);
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/api/auth/login", new LoginHandler(userAuthenticator));
server.createContext("/api/auth/logout", new LogoutHandler(userAuthenticator));
server.createContext("/api/config", new ConfigHandler(configStore, userAuthenticator));
server.createContext("/api/test-login", new TestLoginHandler(configStore, userAuthenticator));
server.createContext("/api/xtream", new XtreamProxyHandler(configStore, userAuthenticator));
server.createContext("/api/stream-url", new StreamUrlHandler(configStore, userAuthenticator));
server.createContext("/api/stream-proxy", new StreamProxyHandler(userAuthenticator));
server.createContext("/api/open-in-player", new OpenInPlayerHandler(configStore, userAuthenticator));
server.createContext("/api/library/load", new LibraryLoadHandler(libraryService, userAuthenticator));
server.createContext("/api/library/status", new LibraryStatusHandler(libraryService, userAuthenticator));
server.createContext("/api/library/categories", new LibraryCategoriesHandler(libraryService, userAuthenticator));
server.createContext("/api/library/items", new LibraryItemsHandler(libraryService, userAuthenticator));
server.createContext("/api/library/search", new LibrarySearchHandler(libraryService, userAuthenticator));
server.createContext("/api/library/series-episodes", new LibrarySeriesEpisodesHandler(libraryService, userAuthenticator));
server.createContext("/api/library/epg", new LibraryEpgHandler(libraryService, userAuthenticator));
server.createContext("/api/favorites", new FavoritesHandler(libraryService, userAuthenticator));
server.createContext("/user", new UserCrudHandler(applicationDao));
server.createContext("/api/auth/login", corsWrapper(new LoginHandler(userAuthenticator)));
server.createContext("/api/auth/logout", corsWrapper(new LogoutHandler(userAuthenticator)));
server.createContext("/api/config", corsWrapper(new ConfigHandler(configStore, userAuthenticator)));
server.createContext("/api/test-login", corsWrapper(new TestLoginHandler(configStore, userAuthenticator)));
server.createContext("/api/xtream", corsWrapper(new XtreamProxyHandler(configStore, userAuthenticator)));
server.createContext("/api/stream-url", corsWrapper(new StreamUrlHandler(configStore, userAuthenticator)));
server.createContext("/api/stream-proxy", corsWrapper(new StreamProxyHandler(userAuthenticator)));
server.createContext("/api/open-in-player", corsWrapper(new OpenInPlayerHandler(configStore, userAuthenticator)));
server.createContext("/api/library/load", corsWrapper(new LibraryLoadHandler(libraryService, userAuthenticator)));
server.createContext("/api/library/status", corsWrapper(new LibraryStatusHandler(libraryService, userAuthenticator)));
server.createContext("/api/library/categories", corsWrapper(new LibraryCategoriesHandler(libraryService, userAuthenticator)));
server.createContext("/api/library/items", corsWrapper(new LibraryItemsHandler(libraryService, userAuthenticator)));
server.createContext("/api/library/search", corsWrapper(new LibrarySearchHandler(libraryService, userAuthenticator)));
server.createContext("/api/library/series-episodes", corsWrapper(new LibrarySeriesEpisodesHandler(libraryService, userAuthenticator)));
server.createContext("/api/library/epg", corsWrapper(new LibraryEpgHandler(libraryService, userAuthenticator)));
server.createContext("/api/favorites", corsWrapper(new FavoritesHandler(libraryService, userAuthenticator)));
server.createContext("/user", corsWrapper(new UserCrudHandler(applicationDao)));
server.createContext("/", new StaticHandler(userAuthenticator));
server.setExecutor(Executors.newFixedThreadPool(12));
server.start();
@ -104,6 +104,18 @@ public final class XtreamPlayerApplication {
LOGGER.info("Xtream Player started on http://localhost:{}", port);
}
private static HttpHandler corsWrapper(HttpHandler delegate) {
return exchange -> {
addCorsHeaders(exchange);
if ("OPTIONS".equalsIgnoreCase(exchange.getRequestMethod())) {
exchange.sendResponseHeaders(200, -1);
exchange.close();
} else {
delegate.handle(exchange);
}
};
}
private static int resolvePort() {
String raw = System.getenv("PORT");
if (raw == null || raw.isBlank()) {
@ -1506,12 +1518,20 @@ public final class XtreamPlayerApplication {
}
private static void writeBytes(HttpExchange exchange, int statusCode, byte[] bytes, String contentType) throws IOException {
addCorsHeaders(exchange);
exchange.sendResponseHeaders(statusCode, bytes.length);
exchange.getResponseBody().write(bytes);
logApiResponse(exchange, statusCode, bytes.length, contentType);
exchange.close();
}
private static void addCorsHeaders(HttpExchange exchange) {
exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*");
exchange.getResponseHeaders().set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
exchange.getResponseHeaders().set("Access-Control-Max-Age", "86400");
}
private static String contentType(String resourcePath) {
if (resourcePath.endsWith(".html")) {
return "text/html; charset=utf-8";