web server tweaks
This commit is contained in:
parent
af58eb1987
commit
cda8a5ec6d
262
docs/webserver-api-reference.md
Normal file
262
docs/webserver-api-reference.md
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
# CrossPointWebServer API Reference
|
||||||
|
|
||||||
|
Source: `src/network/CrossPointWebServer.cpp` and `CrossPointWebServer.h`
|
||||||
|
|
||||||
|
## Server Configuration
|
||||||
|
|
||||||
|
- HTTP port: 80 (default)
|
||||||
|
- WebSocket port: 81 (default)
|
||||||
|
- WiFi sleep disabled for responsiveness
|
||||||
|
- Supports both STA (station) and AP (access point) modes
|
||||||
|
|
||||||
|
## HTTP Endpoints
|
||||||
|
|
||||||
|
### GET /
|
||||||
|
**Handler:** `handleRoot()`
|
||||||
|
**Response:** HTML homepage from `HomePageHtml` (generated from `html/HomePage.html`)
|
||||||
|
**Content-Type:** text/html
|
||||||
|
|
||||||
|
### GET /files
|
||||||
|
**Handler:** `handleFileList()`
|
||||||
|
**Response:** HTML file browser page from `FilesPageHtml` (generated from `html/FilesPage.html`)
|
||||||
|
**Content-Type:** text/html
|
||||||
|
|
||||||
|
### GET /api/status
|
||||||
|
**Handler:** `handleStatus()`
|
||||||
|
**Response:** JSON device status
|
||||||
|
**Content-Type:** application/json
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "CROSSPOINT_VERSION",
|
||||||
|
"ip": "192.168.x.x",
|
||||||
|
"mode": "AP" | "STA",
|
||||||
|
"rssi": -50, // 0 in AP mode
|
||||||
|
"freeHeap": 123456,
|
||||||
|
"uptime": 3600 // seconds
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /api/files
|
||||||
|
**Handler:** `handleFileListData()`
|
||||||
|
**Query params:**
|
||||||
|
- `path` (optional): Directory path, defaults to "/"
|
||||||
|
- `showHidden` (optional): "true" to show dot-files (except .crosspoint)
|
||||||
|
**Response:** JSON array of files
|
||||||
|
**Content-Type:** application/json
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"name": "book.epub", "size": 123456, "isDirectory": false, "isEpub": true},
|
||||||
|
{"name": "folder", "size": 0, "isDirectory": true, "isEpub": false}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
**Notes:**
|
||||||
|
- Hidden by default: files starting with ".", "System Volume Information", "XTCache"
|
||||||
|
- Always hidden: ".crosspoint" (internal cache folder)
|
||||||
|
- Streamed response (chunked encoding) to reduce memory usage
|
||||||
|
|
||||||
|
### GET /api/archived
|
||||||
|
**Handler:** `handleArchivedList()`
|
||||||
|
**Response:** JSON array of archived books
|
||||||
|
**Content-Type:** application/json
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"filename": "archived_file.epub", "originalPath": "/Books/archived_file.epub"}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
**Notes:** Uses `BookManager::listArchivedBooks()` and `BookManager::getArchivedBookOriginalPath()`
|
||||||
|
|
||||||
|
### GET /download
|
||||||
|
**Handler:** `handleDownload()`
|
||||||
|
**Query params:**
|
||||||
|
- `path` (required): File path to download
|
||||||
|
**Response:** File binary with Content-Disposition attachment header
|
||||||
|
**Content-Type:** application/octet-stream
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing path, path is directory
|
||||||
|
- 403: Hidden/system file, protected item
|
||||||
|
- 404: File not found
|
||||||
|
**Notes:**
|
||||||
|
- Streams in 4KB chunks
|
||||||
|
- Updates `totalBytesDownloaded` and `totalFilesDownloaded` stats
|
||||||
|
- Security: rejects paths with "..", files starting with ".", protected items
|
||||||
|
|
||||||
|
### POST /upload
|
||||||
|
**Handler:** `handleUpload()` (multipart handler), `handleUploadPost()` (response handler)
|
||||||
|
**Query params:**
|
||||||
|
- `path` (optional): Upload directory, defaults to "/"
|
||||||
|
**Form data:** multipart/form-data with file
|
||||||
|
**Response:** "File uploaded successfully: filename" or error message
|
||||||
|
**Notes:**
|
||||||
|
- Uses 4KB write buffer for SD card efficiency
|
||||||
|
- Overwrites existing files
|
||||||
|
- Clears epub cache after upload via `clearEpubCacheIfNeeded()`
|
||||||
|
- Updates `totalBytesUploaded` and `totalFilesUploaded` stats
|
||||||
|
- Logs progress every 100KB
|
||||||
|
|
||||||
|
### POST /mkdir
|
||||||
|
**Handler:** `handleCreateFolder()`
|
||||||
|
**Form params:**
|
||||||
|
- `name` (required): Folder name
|
||||||
|
- `path` (optional): Parent directory, defaults to "/"
|
||||||
|
**Response:** "Folder created: foldername" or error
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing name, empty name, folder exists
|
||||||
|
|
||||||
|
### POST /delete
|
||||||
|
**Handler:** `handleDelete()`
|
||||||
|
**Form params:**
|
||||||
|
- `path` (required): Item path to delete
|
||||||
|
- `type` (optional): "file" (default) or "folder"
|
||||||
|
- `archived` (optional): "true" for archived books
|
||||||
|
**Response:** "Deleted successfully" or error
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing path, root directory, folder not empty
|
||||||
|
- 403: Hidden/system file, protected item
|
||||||
|
- 404: Item not found
|
||||||
|
- 500: Delete failed
|
||||||
|
**Notes:**
|
||||||
|
- For files: uses `BookManager::deleteBook()` which handles cache and recent books cleanup
|
||||||
|
- For folders: must be empty first
|
||||||
|
- For archived: passes filename to `BookManager::deleteBook(filename, true)`
|
||||||
|
|
||||||
|
### POST /archive
|
||||||
|
**Handler:** `handleArchive()`
|
||||||
|
**Form params:**
|
||||||
|
- `path` (required): Book path to archive
|
||||||
|
**Response:** "Book archived successfully" or error
|
||||||
|
**Notes:** Uses `BookManager::archiveBook()`
|
||||||
|
|
||||||
|
### POST /unarchive
|
||||||
|
**Handler:** `handleUnarchive()`
|
||||||
|
**Form params:**
|
||||||
|
- `filename` (required): Archived book filename
|
||||||
|
**Response:** JSON with original path
|
||||||
|
**Content-Type:** application/json
|
||||||
|
```json
|
||||||
|
{"success": true, "originalPath": "/Books/book.epub"}
|
||||||
|
```
|
||||||
|
**Notes:** Uses `BookManager::unarchiveBook()` and `BookManager::getArchivedBookOriginalPath()`
|
||||||
|
|
||||||
|
### POST /rename
|
||||||
|
**Handler:** `handleRename()`
|
||||||
|
**Form params:**
|
||||||
|
- `path` (required): Current item path
|
||||||
|
- `newName` (required): New name (filename only, no path separators)
|
||||||
|
**Response:** "Renamed successfully" or error
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing params, empty name, name contains "/" or "\\", root directory, destination exists
|
||||||
|
- 403: System file, protected item
|
||||||
|
- 404: Source not found
|
||||||
|
- 500: Rename failed
|
||||||
|
**Notes:**
|
||||||
|
- Renames in place (same directory, new name)
|
||||||
|
- Uses `SdMan.rename()`
|
||||||
|
- Clears epub cache after rename via `clearEpubCacheIfNeeded()`
|
||||||
|
|
||||||
|
### POST /copy
|
||||||
|
**Handler:** `handleCopy()`
|
||||||
|
**Form params:**
|
||||||
|
- `srcPath` (required): Source path
|
||||||
|
- `destPath` (required): Full destination path (including new name)
|
||||||
|
**Response:** "Copied successfully" or error
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing params, root directory, destination exists, copy into self
|
||||||
|
- 403: System file, protected item
|
||||||
|
- 404: Source not found
|
||||||
|
- 500: Copy failed
|
||||||
|
**Notes:**
|
||||||
|
- Uses `copyFile()` for files (4KB buffer chunks)
|
||||||
|
- Uses `copyFolder()` for recursive directory copy
|
||||||
|
- Skips hidden files in folder copy
|
||||||
|
|
||||||
|
### POST /move
|
||||||
|
**Handler:** `handleMove()`
|
||||||
|
**Form params:**
|
||||||
|
- `srcPath` (required): Source path
|
||||||
|
- `destPath` (required): Full destination path (including new name)
|
||||||
|
**Response:** "Moved successfully" or error
|
||||||
|
**Errors:** Same as copy
|
||||||
|
**Notes:**
|
||||||
|
- First attempts atomic `SdMan.rename()` (fast)
|
||||||
|
- Falls back to copy+delete if rename fails
|
||||||
|
- Uses `deleteFolderRecursive()` for folder cleanup
|
||||||
|
|
||||||
|
## WebSocket Protocol (port 81)
|
||||||
|
|
||||||
|
**Handler:** `onWebSocketEvent()` via `wsEventCallback()` trampoline
|
||||||
|
|
||||||
|
### Upload Protocol
|
||||||
|
|
||||||
|
1. Client connects
|
||||||
|
2. Server: (implicit connection acknowledgment)
|
||||||
|
3. Client TEXT: `START:<filename>:<size>:<path>`
|
||||||
|
4. Server TEXT: `READY` or `ERROR:<message>`
|
||||||
|
5. Client BIN: file data chunks (any size, recommend 64KB)
|
||||||
|
6. Server TEXT: `PROGRESS:<received>:<total>` (every 64KB or at end)
|
||||||
|
7. Server TEXT: `DONE` or `ERROR:<message>`
|
||||||
|
|
||||||
|
### Events
|
||||||
|
- `WStype_CONNECTED`: Client connected, logs connection
|
||||||
|
- `WStype_DISCONNECTED`: Cleanup incomplete upload, delete partial file
|
||||||
|
- `WStype_TEXT`: Parse control messages (START)
|
||||||
|
- `WStype_BIN`: Write file data, send progress, complete upload
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
- Faster than HTTP multipart for large files
|
||||||
|
- Direct binary writes to SD card
|
||||||
|
- Clears epub cache after upload
|
||||||
|
- Updates traffic statistics
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
### Protected Items (HIDDEN_ITEMS[])
|
||||||
|
- "System Volume Information"
|
||||||
|
- "XTCache"
|
||||||
|
|
||||||
|
### Always Hidden
|
||||||
|
- ".crosspoint" (internal cache)
|
||||||
|
|
||||||
|
### Security Checks Applied To
|
||||||
|
- `/delete`: Rejects dot-files (unless archived), protected items
|
||||||
|
- `/download`: Rejects dot-files, protected items, path traversal (..)
|
||||||
|
- `/rename`: Rejects dot-files, protected items
|
||||||
|
- `/copy`: Rejects dot-files, protected items
|
||||||
|
- `/move`: Rejects dot-files, protected items
|
||||||
|
|
||||||
|
## Helper Functions
|
||||||
|
|
||||||
|
### clearEpubCacheIfNeeded(filePath)
|
||||||
|
- Location: anonymous namespace at top of file
|
||||||
|
- Clears epub cache if file ends with ".epub"
|
||||||
|
- Uses `Epub(filePath, "/.crosspoint").clearCache()`
|
||||||
|
- Called by: upload, WebSocket upload, rename
|
||||||
|
|
||||||
|
### scanFiles(path, callback, showHidden)
|
||||||
|
- Iterates directory, calls callback for each FileInfo
|
||||||
|
- Yields and resets watchdog during iteration
|
||||||
|
- Filters hidden items based on showHidden flag
|
||||||
|
|
||||||
|
### copyFile(srcPath, destPath) / copyFolder(srcPath, destPath)
|
||||||
|
- 4KB buffer for file copy
|
||||||
|
- Recursive for folders
|
||||||
|
- Returns bool success
|
||||||
|
|
||||||
|
### deleteFolderRecursive(path)
|
||||||
|
- Static helper for move fallback
|
||||||
|
- Recursively deletes contents then directory
|
||||||
|
|
||||||
|
## Traffic Statistics (mutable, updated from const handlers)
|
||||||
|
- `totalBytesUploaded`
|
||||||
|
- `totalBytesDownloaded`
|
||||||
|
- `totalFilesUploaded`
|
||||||
|
- `totalFilesDownloaded`
|
||||||
|
- `serverStartTime` (for uptime calculation)
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- `<WebServer.h>` - ESP32 HTTP server
|
||||||
|
- `<WebSocketsServer.h>` - WebSocket support
|
||||||
|
- `<ArduinoJson.h>` - JSON serialization
|
||||||
|
- `<SDCardManager.h>` - SD card operations (SdMan singleton)
|
||||||
|
- `<Epub.h>` - Epub cache management
|
||||||
|
- `BookManager.h` - Book deletion, archiving, recent books
|
||||||
|
- `StringUtils.h` - File extension checking
|
||||||
@ -77,7 +77,7 @@ void CrossPointWebServerActivity::onEnter() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&CrossPointWebServerActivity::taskTrampoline, "WebServerActivityTask",
|
xTaskCreate(&CrossPointWebServerActivity::taskTrampoline, "WebServerActivityTask",
|
||||||
2048, // Stack size
|
6144, // Stack size (increased: QR code + string ops need ~4KB)
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
&displayTaskHandle // Task handle
|
&displayTaskHandle // Task handle
|
||||||
|
|||||||
@ -1143,6 +1143,11 @@ void CrossPointWebServer::handleRename() const {
|
|||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
if (SdMan.rename(itemPath.c_str(), newPath.c_str())) {
|
if (SdMan.rename(itemPath.c_str(), newPath.c_str())) {
|
||||||
Serial.printf("[%lu] [WEB] Rename successful\n", millis());
|
Serial.printf("[%lu] [WEB] Rename successful\n", millis());
|
||||||
|
|
||||||
|
// Clear epub cache for both old and new paths to prevent stale metadata
|
||||||
|
clearEpubCacheIfNeeded(itemPath); // Old path cache is now invalid
|
||||||
|
clearEpubCacheIfNeeded(newPath); // Ensure clean cache for new path
|
||||||
|
|
||||||
server->send(200, "text/plain", "Renamed successfully");
|
server->send(200, "text/plain", "Renamed successfully");
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [WEB] Rename failed\n", millis());
|
Serial.printf("[%lu] [WEB] Rename failed\n", millis());
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user