feat: Implement core server application with API routing, streaming, and static content serving.

This commit is contained in:
Michael committed 2026-01-07 11:29:00 +01:00
1 parent 1dd77bd87a
commit 755367018a
3 files changed
+55 -52

No files matched your search

+30 -30
View File
@@ -108,41 +108,41 @@ class ProxyHandler {
final proxyHeaders = {
'User-Agent': 'VLC/3.0.18 LibVLC/3.0.18',
'Accept': '*/*',
'Accept-Encoding': 'identity',
'Connection': 'keep-alive',
'Accept-Encoding': 'identity', // Explicitly request non-chunked
'Connection': 'close', // Don't keep-alive to avoid chunked issues
};
final client = http.Client();
final proxyRequest = http.Request(request.method, targetUrl);
proxyRequest.headers.addAll(proxyHeaders);
// Forward body if POST
if (request.method == 'POST') {
final bodyBytes = await request.read().toList();
proxyRequest.bodyBytes = bodyBytes.expand((i) => i).toList();
try {
// Use simple GET/POST instead of streaming to avoid chunked encoding issues
http.Response response;
if (request.method == 'POST') {
final bodyBytes = await request.read().toList();
final body = bodyBytes.expand((i) => i).toList();
response = await client.post(targetUrl, headers: proxyHeaders, body: body);
} else {
response = await client.get(targetUrl, headers: proxyHeaders);
}
// Build response headers
final responseHeaders = <String, String>{
'access-control-allow-origin': '*',
'content-type': response.headers['content-type'] ?? 'application/json',
};
if (response.headers['content-length'] != null) {
responseHeaders['content-length'] = response.headers['content-length']!;
}
return Response(
response.statusCode,
body: response.bodyBytes,
headers: responseHeaders,
);
} finally {
client.close();
}
// Streaming Response
final streamedResponse = await client.send(proxyRequest);
// Filter headers
final responseHeaders = <String, String>{
'access-control-allow-origin': '*',
};
streamedResponse.headers.forEach((key, value) {
if (_allowedHeaders.contains(key.toLowerCase())) {
responseHeaders[key] = value;
}
});
// Pipe the stream directly to the response
return Response(
streamedResponse.statusCode,
body: streamedResponse.stream,
headers: responseHeaders,
);
} catch (e, stackTrace) {
print('Proxy error: $e');
print(stackTrace);
+7 -7
View File
@@ -46,24 +46,23 @@ void main(List<String> args) async {
final user = request.context['user'] as User?;
// If we have a user from auth middleware, prefer their playlists
if (user != null) {
print('[getPlaylist] User from context: ${user.username}');
final playlists = db.getPlaylists(user.id);
if (playlists.isNotEmpty) playlist = playlists.first;
} else {
// Fallback: This leg should effectively be unreachable for /api/xtream if we use authMiddleware,
// but might be used by other handlers or if auth is optional somewhere.
// For VOD/Live which currently might not have auth (URL tokenizing is complex), we keep fallback?
// WAIT: The plan said "Secure /api/xtream using authMiddleware".
// If we do that, user is GUARANTEED for proxy.
// But for /api/live and /api/vod, we might need a different strategy (token in URL).
// For now, let's keep the fallback logic for non-proxy parts if any.
// Fallback for proxy: get first user's first playlist
print('[getPlaylist] No user in context, using fallback');
final users = db.getAllUsers();
print('[getPlaylist] Total users in DB: ${users.length}');
if (users.isNotEmpty) {
final playlists = db.getPlaylists(users[0].id);
print('[getPlaylist] User ${users[0].username} has ${playlists.length} playlists');
if (playlists.isNotEmpty) playlist = playlists.first;
}
}
if (playlist != null) {
print('[getPlaylist] Returning playlist: ${playlist.name} (DNS: ${playlist.serverUrl})');
return PlaylistConfig(
id: playlist.id,
name: playlist.name,
@@ -74,6 +73,7 @@ void main(List<String> args) async {
isActive: true,
);
}
print('[getPlaylist] WARNING: No playlist found, returning null');
return null;
}
+18 -15
View File
@@ -1,21 +1,24 @@
# Fix Docker Database & Proxy Issues
# Fix Docker & Proxy Issues
## Context
## Completed
1. SQLite database read-only error on login (permissions)
2. Xtream proxy 401 Unauthorized errors
- [x] Database permissions (`entrypoint.sh` + `Dockerfile`)
- [x] Proxy 401 errors (removed broken mount in `server.dart`)
- [x] Proxy chunked encoding error (buffered response in `proxy_handler.dart`)
## Master Plan
## Changes
- [x] Analyze database readonly error → volume permissions mismatch
- [x] Create `entrypoint.sh` with gosu for privilege drop
- [x] Update `Dockerfile` to use entrypoint
- [x] Analyze proxy 401 errors → broken mount in apiRouter
- [x] Remove duplicate `/api/xtream` mount from `server.dart`
- [ ] Rebuild and deploy
| File | Change |
|------|--------|
| `entrypoint.sh` | NEW - fixes /app/data permissions, runs as xtremuser via gosu |
| `Dockerfile` | Added gosu, ENTRYPOINT |
| `bin/server.dart` | Removed broken /api/xtream mount, added logging |
| `bin/api/proxy_handler.dart` | Changed from streaming to buffered response |
## Changes Made
## Deploy
- `entrypoint.sh`: Fixes /app/data permissions at startup, then runs as xtremuser
- `Dockerfile`: Added gosu, ENTRYPOINT, removed USER directive
- `server.dart`: Removed broken /api/xtream mount that was blocking proxy
```bash
git add .
git commit -m "fix: database permissions + proxy errors"
git push && docker compose up --build -d
```