PsychichHTTP v2-dev

This commit is contained in:
iranl
2024-12-30 14:37:09 +01:00
parent 2cf5201285
commit 78459c2d08
118 changed files with 5453 additions and 4972 deletions

View File

@@ -1,66 +1,73 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PsychicHTTP Demo</title>
<link rel="icon" href="./favicon.ico" type="image/x-icon">
</head>
<body>
<main>
<h1>Basic Request Examples</h1>
<ul>
<li><a href="/api?foo=bar">API Call</a></li>
<li><a href="/redirect">Redirection</a></li>
<li><a href="/auth-digest">Authentication (Digest)</a></li>
<li><a href="/auth-basic">Authentication (Basic)</a></li>
<li><a href="/cookies">Cookies Demo</a></li>
<li><a href="/404">404</a></li>
</ul>
<h1>Static Serving</h1>
<p>
<a href="/alien.png"><img width="60" src="/alien.png"></a>
<a href="/img/request_flow.png"><img width="60" src="/img/request_flow.png"></a>
</p>
<p><a href="/myfile.txt">Text File</a></p>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PsychicHTTP Demo</title>
<link rel="icon" href="./favicon.ico" type="image/x-icon">
</head>
<h1>Simple POST Form</h1>
<form action="/post" method="post">
<label for="param1">Parameter 1:</label>
<input type="text" id="param1" name="param1" value="Parameter 1">
<br>
<label for="param2">Parameter 2:</label>
<input type="text" id="param2" name="param2" value="Parameter 2">
<br>
<input type="submit" value="Submit">
</form>
<body>
<main>
<h1>Basic Request Examples</h1>
<ul>
<li><a href="/api?foo=bar">API Call</a></li>
<li><a href="/redirect">Redirection</a></li>
<li><a href="/auth-digest">Authentication (Digest)</a></li>
<li><a href="/auth-basic">Authentication (Basic)</a></li>
<li><a href="/cookies">Cookies Demo</a></li>
<li><a href="/404">404</a></li>
</ul>
<h1>Basic File Upload</h1>
<table border="0">
<tr>
<td>
<label for="newfile">Upload a file</label>
</td>
<td colspan="2">
<input id="newfile" type="file" onchange="setpath()" style="width:100%;">
</td>
</tr>
<tr>
<td>
<label for="filepath">Set path on server</label>
</td>
<td>
<input id="filepath" type="text" style="width:100%;">
</td>
</tr>
<tr>
<td>
<button id="upload" type="button" onclick="upload()">Upload</button>
</td>
</tr>
</table>
<script>
<h1>Utilities</h1>
<ul>
<li><a href="/websocket-test.html">WebSocket Tester</a></li>
</ul>
<h1>Static Serving</h1>
<p>
<a href="/alien.png"><img width="60" src="/alien.png"></a>
<a href="/img/request_flow.png"><img width="60" src="/img/request_flow.png"></a>
</p>
<p><a href="/myfile.txt">Text File</a></p>
<h1>Simple POST Form</h1>
<form action="/post" method="post">
<label for="param1">Parameter 1:</label>
<input type="text" id="param1" name="param1" value="Parameter 1">
<br>
<label for="param2">Parameter 2:</label>
<input type="text" id="param2" name="param2" value="Parameter 2">
<br>
<input type="submit" value="Submit">
</form>
<h1>Basic File Upload</h1>
<table border="0">
<tr>
<td>
<label for="newfile">Upload a file</label>
</td>
<td colspan="2">
<input id="newfile" type="file" onchange="setpath()" style="width:100%;">
</td>
</tr>
<tr>
<td>
<label for="filepath">Set path on server</label>
</td>
<td>
<input id="filepath" type="text" style="width:100%;">
</td>
</tr>
<tr>
<td>
<button id="upload" type="button" onclick="upload()">Upload</button>
</td>
</tr>
</table>
<script>
function setpath() {
var default_path = document.getElementById("newfile").files[0].name;
document.getElementById("filepath").value = default_path;
@@ -72,7 +79,7 @@
/* Max size of an individual file. Make sure this
* value is same as that set in file_server.c */
var MAX_FILE_SIZE = 2048*1024;
var MAX_FILE_SIZE = 2048 * 1024;
var MAX_FILE_SIZE_STR = "2MB";
if (fileInput.length == 0) {
@@ -81,9 +88,9 @@
alert("File path on server is not set!");
} else if (filePath.indexOf(' ') >= 0) {
alert("File path on server cannot have spaces!");
} else if (filePath[filePath.length-1] == '/') {
} else if (filePath[filePath.length - 1] == '/') {
alert("File name not specified after path!");
} else if (fileInput[0].size > 200*1024) {
} else if (fileInput[0].size > 200 * 1024) {
alert("File size must be less than 200KB!");
} else {
document.getElementById("newfile").disabled = true;
@@ -92,7 +99,7 @@
var file = fileInput[0];
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
xhttp.onreadystatechange = function () {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
document.open();
@@ -111,126 +118,125 @@
xhttp.send(file);
}
}
</script>
</script>
<h1>Multipart POST Form</h1>
<form action="/multipart" method="post" enctype="multipart/form-data">
<label for="param1">Parameter 1:</label>
<input type="text" id="param1" name="param1" value="Parameter 1">
<br>
<h1>Multipart POST Form</h1>
<form action="/multipart" method="post" enctype="multipart/form-data">
<label for="param1">Parameter 1:</label>
<input type="text" id="param1" name="param1" value="Parameter 1">
<br>
<label for="param2">Parameter 2:</label>
<input type="text" id="param2" name="param2" value="Parameter 2">
<br>
<label for="param2">Parameter 2:</label>
<input type="text" id="param2" name="param2" value="Parameter 2">
<br>
<label for="file-upload">File Upload:</label>
<input type="file" id="file-upload" name="file_upload" accept=".txt, .html, .pdf, .png, .jpg, .gif" required>
<br>
<input type="submit" value="Upload File">
</form>
<label for="file-upload">File Upload:</label>
<input type="file" id="file-upload" name="file_upload" accept=".txt, .html, .pdf, .png, .jpg, .gif"
required>
<br>
<h1>Websocket Demo</h1>
<input type="text" id="message_input" placeholder="Type your message">
<button onclick="sendMessage()">Send Message</button>
<button onclick="websocketConnect()">Connect</button>
<div>
<textarea id="websocket_output" style="width: 50%; height: 250px;"></textarea>
</div>
<input type="submit" value="Upload File">
</form>
<script>
let socket;
const outputText = document.getElementById('websocket_output');
const messageInput = document.getElementById('message_input');
<h1>Websocket Demo</h1>
<input type="text" id="message_input" placeholder="Type your message">
<button onclick="sendMessage()">Send Message</button>
<button onclick="websocketConnect()">Connect</button>
<div>
<textarea id="websocket_output" style="width: 50%; height: 250px;"></textarea>
</div>
function websocketConnect()
{
// Create a WebSocket connection
socket = new WebSocket((location.protocol === "https:" ? "wss://" : "ws://") + window.location.host + "/ws");
// Event handler for when the WebSocket connection is open
socket.addEventListener('open', (event) => {
outputText.value += `[socket] connection opened!\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when a message is received from the WebSocket server
socket.addEventListener('message', (event) => {
// Echo the received message into the output div
let data = event.data.trim();
outputText.value += `[received] ${data}\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when an error occurs with the WebSocket connection
socket.addEventListener('error', (event) => {
let data = event.data.trim();
outputText.value += `[error] ${event.data}\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when the WebSocket connection is closed
socket.addEventListener('close', (event) => {
outputText.value += `[socket] connection closed!\n`;
});
}
// Function to send a message to the WebSocket server
function sendMessage() {
if (socket.readyState == WebSocket.OPEN) {
const message = messageInput.value.trim();
if (message) {
socket.send(message);
messageInput.value = ''; // Clear the input field after sending the message
outputText.value += `[sent] ${message}\n`;
outputText.scrollTop = outputText.scrollHeight;
}
}
else
{
outputText.value += `[error] Not Connected\n`;
<script>
let socket;
const outputText = document.getElementById('websocket_output');
const messageInput = document.getElementById('message_input');
function websocketConnect() {
// Create a WebSocket connection
socket = new WebSocket((location.protocol === "https:" ? "wss://" : "ws://") + window.location.host + "/ws");
// Event handler for when the WebSocket connection is open
socket.addEventListener('open', (event) => {
outputText.value += `[socket] connection opened!\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when a message is received from the WebSocket server
socket.addEventListener('message', (event) => {
// Echo the received message into the output div
let data = event.data.trim();
outputText.value += `[received] ${data}\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when an error occurs with the WebSocket connection
socket.addEventListener('error', (event) => {
let data = event.data.trim();
outputText.value += `[error] ${event.data}\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when the WebSocket connection is closed
socket.addEventListener('close', (event) => {
outputText.value += `[socket] connection closed!\n`;
});
}
// Function to send a message to the WebSocket server
function sendMessage() {
if (socket.readyState == WebSocket.OPEN) {
const message = messageInput.value.trim();
if (message) {
socket.send(message);
messageInput.value = ''; // Clear the input field after sending the message
outputText.value += `[sent] ${message}\n`;
outputText.scrollTop = outputText.scrollHeight;
}
}
</script>
<h1>EventSource Demo</h1>
<button onclick="eventSourceConnect()">Connect</button>
<div>
<textarea id="eventsource_output" style="width: 50%; height: 250px;"></textarea>
</div>
<script>
const dataElement = document.getElementById('eventsource_output');
function eventSourceConnect()
{
const eventSource = new EventSource('/events');
eventSource.onopen = () => {
dataElement.value += `[connected]\n`;
dataElement.scrollTop = dataElement.scrollHeight;
};
eventSource.addEventListener('millis', (event) => {
let data = event.data.trim()
dataElement.value += `[millis] ${data}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
});
eventSource.onmessage = (event) => {
let data = event.data.trim()
dataElement.value += `[message] ${data}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
};
eventSource.onerror = (error) => {
dataElement.value += `[error] ${error}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
eventSource.close();
};
else {
outputText.value += `[error] Not Connected\n`;
outputText.scrollTop = outputText.scrollHeight;
}
</script>
</main>
</body>
}
</script>
<h1>EventSource Demo</h1>
<button onclick="eventSourceConnect()">Connect</button>
<div>
<textarea id="eventsource_output" style="width: 50%; height: 250px;"></textarea>
</div>
<script>
const dataElement = document.getElementById('eventsource_output');
function eventSourceConnect() {
const eventSource = new EventSource('/events');
eventSource.onopen = () => {
dataElement.value += `[connected]\n`;
dataElement.scrollTop = dataElement.scrollHeight;
};
eventSource.addEventListener('millis', (event) => {
let data = event.data.trim()
dataElement.value += `[millis] ${data}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
});
eventSource.onmessage = (event) => {
let data = event.data.trim()
dataElement.value += `[message] ${data}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
};
eventSource.onerror = (error) => {
dataElement.value += `[error] ${error}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
eventSource.close();
};
}
</script>
</main>
</body>
</html>

View File

@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Message Rate Test</title>
</head>
<body>
<h1>WebSocket Message Rate Test</h1>
<p>Time Remaining: <span id="time_remaining">0</span></p>
<p>Messages Count: <span id="message_count">0</span></p>
<p>Messages per second: <span id="rate">0</span></p>
<label for="duration">Test Duration (seconds):</label>
<input type="number" id="duration" value="30" min="1">
<p>
<button id="startTestSmall">Start Test (256b json)</button>
<button id="startTestBig">Start Test (2k json)</button>
</p>
<p id="status"></p>
<script>
let ws;
let messageCount = 0;
let startTime;
let endTime;
let testRunning = false;
let smallPayload = { "user": { "id": 123456789, "name": "JohnDoe", "email": "johndoe@example.com", "preferences": { "theme": "dark", "notifications": { "email": true, "sms": false }, "language": "en" } } };
let bigPayload = { "user": { "id": 123456789, "name": "JohnDoe", "email": "johndoe@example.com", "preferences": { "theme": "dark", "notifications": { "email": true, "sms": false }, "language": "en", "options": { "option1": "value1", "option2": "value2", "option3": "value3", "option4": "value4", "option5": "value5", "option6": "value6", "option7": "value7", "option8": "value8", "option9": "value9", "option10": "value10", "option11": "value11", "option12": "value12", "option13": "value13", "option14": "value14", "option15": "value15", "option16": "value16", "option17": "value17", "option18": "value18", "option19": "value19", "option20": "value20", "option21": "value21", "option22": "value22", "option23": "value23", "option24": "value24", "option25": "value25", "option26": "value26", "option27": "value27", "option28": "value28", "option29": "value29", "option30": "value30", "option31": "value31", "option32": "value32", "option33": "value33", "option34": "value34", "option35": "value35", "option36": "value36", "option37": "value37", "option38": "value38", "option39": "value39", "option40": "value40", "option41": "value41", "option42": "value42", "option43": "value43", "option44": "value44", "option45": "value45", "option46": "value46", "option47": "value47", "option48": "value48", "option49": "value49", "option50": "value50", "option51": "value51", "option52": "value52", "option53": "value53", "option54": "value54", "option55": "value55", "option56": "value56", "option57": "value57", "option58": "value58", "option59": "value59", "option60": "value60", "option61": "value61", "option62": "value62", "option63": "value63", "option64": "value64", "option65": "value65", "option66": "value66", "option67": "value67", "option68": "value68", "option69": "value69", "option70": "value70", "option71": "value71", "option72": "value72", "option73": "value73", "option74": "value74", "option75": "value75", "option76": "value76", "option77": "value77", "option78": "value78", "option79": "value79", "option80": "value80", "option81": "value81", "option82": "value82", "option83": "value83", "option84": "value84", "option85": "value85", "option86": "value86", "option87": "value87", "option88": "value88", "option89": "value89", "option90": "value90", "option91": "value91", "option92": "value92", "option93": "value93", "option94": "value94", "option95": "value95", "option96": "value96", "option97": "value97", "option98": "value98", "option99": "value99", "option100": "value100", "option101": "value101", "option102": "value102", "option103": "value103", "option104": "value104", "option105": "value105", "option106": "value106", "option107": "value107", "option108": "value108", "option109": "value109", "option110": "value110", "option111": "value111", "option112": "value112", "option113": "value113", "option114": "value114", "option115": "value115", "option116": "value116", "option117": "value117", "option118": "value118", "option119": "value119", "option120": "value120" } } } };
let payload;
// Function to update the message rate
function updateRate(force = false) {
const currentTime = Date.now();
const elapsedTime = (currentTime - startTime) / 1000; // in seconds
let remainingTime = (endTime - currentTime) / 1000; // in seconds
remainingTime = Math.max(0, remainingTime);
const rate = messageCount / elapsedTime;
if (rate) {
document.getElementById('rate').innerText = rate.toFixed(2);
document.getElementById('message_count').innerText = messageCount;
document.getElementById('time_remaining').innerText = remainingTime.toFixed(2);
}
if (testRunning)
setTimeout(updateRate, 25);
}
function startTestSmall() {
payload = smallPayload;
startTest();
}
function startTestBig() {
payload = bigPayload;
startTest();
}
// Function to start the WebSocket connection and the test
function startTest() {
if (testRunning) return;
console.log("Payload length: " + JSON.stringify(payload).length);
document.getElementById('startTestSmall').disabled = true;
document.getElementById('startTestBig').disabled = true;
document.getElementById('status').innerText = 'Connecting';
const durationInput = document.getElementById('duration').value;
const testDuration = parseInt(durationInput) * 1000 || 60000; // default to 60 seconds if invalid
// Determine the WebSocket protocol based on the current protocol
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
ws = new WebSocket(`${protocol}${window.location.host}/ws`);
ws.onopen = function () {
document.getElementById('status').innerText = 'Connected';
startTime = Date.now();
endTime = startTime + testDuration;
messageCount = 0;
testRunning = true;
sendAndReceiveMessage();
updateRate();
};
ws.onmessage = function (event) {
try {
const parsedData = JSON.parse(event.data);
if (parsedData.user) {
messageCount++;
if (testRunning) {
sendAndReceiveMessage();
}
}
} catch (err) { }
};
ws.onerror = function (error) {
document.getElementById('status').innerText = 'Error: ' + error.message;
};
ws.onclose = function () {
//document.getElementById('status').innerText = 'Connection Closed';
};
}
// Function to send a message and wait for the response
function sendAndReceiveMessage() {
if (Date.now() >= endTime) {
testRunning = false;
document.getElementById('startTestSmall').disabled = false;
document.getElementById('startTestBig').disabled = false;
if (ws.readyState === WebSocket.OPEN) {
ws.close();
}
document.getElementById('status').innerText = 'Test Finished';
updateRate(true);
} else {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
}
}
document.getElementById('startTestSmall').addEventListener('click', startTestSmall);
document.getElementById('startTestBig').addEventListener('click', startTestBig);
</script>
</body>
</html>

View File

@@ -1,39 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -12,19 +12,44 @@
platform = espressif32
framework = arduino
board = esp32-s3-devkitc-1
upload_port = /dev/ttyACM0
monitor_port = /dev/ttyACM1
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
lib_deps =
; devmode: with this disabled make a symlink from platformio/lib to the PsychicHttp directory
;hoeken/PsychicHttp
bblanchon/ArduinoJson
; hoeken/PsychicHttp
; PIO is not able to consider installed project in CI
;../..
board_build.filesystem = littlefs
[env:default]
build_flags =
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN
;-D ENABLE_ASYNC
-Wall
-Wextra
; [env:arduino3]
; platform = https://github.com/platformio/platform-espressif32.git
; platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#master
[env:arduino2]
platform = espressif32@6.8.1
[env:arduino2-ssl]
platform = espressif32@6.8.1
build_flags = -D PSY_ENABLE_SSL
[env:arduino2-regex]
platform = espressif32@6.8.1
build_flags = -D PSY_ENABLE_REGEX
[env:arduino3]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
[env:arduino3-ssl]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
build_flags = -D PSY_ENABLE_SSL
[env:arduino3-regex]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
build_flags = -D PSY_ENABLE_REGEX
[env:waveshare-4-3-touchscreen]
lib_deps = ${env.lib_deps}
https://github.com/esp-arduino-libs/ESP32_IO_Expander
build_flags =
-D PSY_ENABLE_SDCARD
-D WAVESHARE_43_TOUCH

View File

@@ -9,66 +9,113 @@
*/
/**********************************************************************************************
* Note: this demo relies on various files to be uploaded on the LittleFS partition
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
**********************************************************************************************/
* Note: this demo relies on various files to be uploaded on the LittleFS partition
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
**********************************************************************************************/
#include "_secret.h"
#include <Arduino.h>
#include <WiFi.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <ESPmDNS.h>
#include <esp_sntp.h>
#include "_secret.h"
#include <LittleFS.h>
#include <PsychicHttp.h>
//#include <PsychicHttpsServer.h> //uncomment this to enable HTTPS / SSL
#include <WiFi.h>
#include <esp_sntp.h>
// #define this to enable SD card support
#ifdef PSY_ENABLE_SDCARD
#ifdef WAVESHARE_43_TOUCH
#include <ESP_IOExpander_Library.h>
// Extend IO Pin define
#define TP_RST 1
#define LCD_BL 2
#define LCD_RST 3
#define SD_CS 4
#define USB_SEL 5
// I2C Pin define
#define I2C_MASTER_NUM I2C_NUM_0
#define I2C_MASTER_SDA_IO 8
#define I2C_MASTER_SCL_IO 9
#define SD_MOSI 11
#define SD_CLK 12
#define SD_MISO 13
#define SD_SS -1
#else
#define SD_MOSI 11
#define SD_CLK 12
#define SD_MISO 13
#define SD_SS 5
#endif
#include <FS.h>
#include <SD.h>
#include <SPI.h>
#endif
// #define this to enable SSL at build (or switch to the 'ssl' build target in vscode)
#ifdef PSY_ENABLE_SSL
#include <PsychicHttpsServer.h>
#endif
// debugging library
#ifdef PSY_DEVMODE
#include <ArduinoTrace.h>
#endif
#ifndef WIFI_SSID
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
#endif
//Enter your WIFI credentials in secret.h
const char *ssid = WIFI_SSID;
const char *password = WIFI_PASS;
// Enter your WIFI credentials in secret.h
const char* ssid = WIFI_SSID;
const char* password = WIFI_PASS;
// Set your SoftAP credentials
const char *softap_ssid = "PsychicHttp";
const char *softap_password = "";
IPAddress softap_ip(10, 0, 0, 1);
const char* softap_ssid = "PsychicHttp";
const char* softap_password = "";
IPAddress softap_ip(10, 0, 0, 1);
//credentials for the /auth-basic and /auth-digest examples
const char *app_user = "admin";
const char *app_pass = "admin";
const char *app_name = "Your App";
// credentials for the /auth-basic and /auth-digest examples
const char* app_user = "admin";
const char* app_pass = "admin";
const char* app_name = "Your App";
//hostname for mdns (psychic.local)
const char *local_hostname = "psychic";
LoggingMiddleware loggingMiddleware;
AuthenticationMiddleware basicAuth;
AuthenticationMiddleware digestAuth;
//#define PSY_ENABLE_SSL to enable ssl
// hostname for mdns (psychic.local)
const char* local_hostname = "psychic";
// #define PSY_ENABLE_SSL to enable ssl
#ifdef PSY_ENABLE_SSL
bool app_enable_ssl = true;
String server_cert;
String server_key;
bool app_enable_ssl = true;
String server_cert;
String server_key;
#endif
//our main server object
// our main server object
#ifdef PSY_ENABLE_SSL
PsychicHttpsServer server;
PsychicHttpsServer server;
#else
PsychicHttpServer server;
PsychicHttpServer server;
#endif
PsychicWebSocketHandler websocketHandler;
PsychicEventSource eventSource;
CorsMiddleware corsMiddleware;
//NTP server stuff
const char *ntpServer1 = "pool.ntp.org";
const char *ntpServer2 = "time.nist.gov";
// NTP server stuff
const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;
struct tm timeinfo;
// Callback function (gets called when time adjusts via NTP)
void timeAvailable(struct timeval *t)
void timeAvailable(struct timeval* t)
{
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time");
@@ -83,10 +130,12 @@ void timeAvailable(struct timeval *t)
bool connectToWifi()
{
//dual client and AP mode
WiFi.mode(WIFI_AP_STA);
// WiFi.mode(WIFI_AP); // ap only mode
// WiFi.mode(WIFI_STA); // client only mode
WiFi.mode(WIFI_AP_STA); // ap and client
// Configure SoftAP
// dual client and AP mode
WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00
WiFi.softAP(softap_ssid, softap_password);
IPAddress myIP = WiFi.softAPIP();
@@ -107,10 +156,8 @@ bool connectToWifi()
int numberOfTries = 20;
// Wait for the WiFi event
while (true)
{
switch (WiFi.status())
{
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL:
Serial.println("[WiFi] SSID not found");
break;
@@ -140,15 +187,12 @@ bool connectToWifi()
}
delay(tryDelay);
if (numberOfTries <= 0)
{
if (numberOfTries <= 0) {
Serial.print("[WiFi] Failed to connect to WiFi!");
// Use disconnect function to force stop trying to connect
WiFi.disconnect();
return false;
}
else
{
} else {
numberOfTries--;
}
}
@@ -156,198 +200,247 @@ bool connectToWifi()
return false;
}
#ifdef PSY_ENABLE_SDCARD
bool setupSDCard()
{
#ifdef WAVESHARE_43_TOUCH
ESP_IOExpander* expander = new ESP_IOExpander_CH422G((i2c_port_t)I2C_MASTER_NUM, ESP_IO_EXPANDER_I2C_CH422G_ADDRESS_000, I2C_MASTER_SCL_IO, I2C_MASTER_SDA_IO);
expander->init();
expander->begin();
expander->multiPinMode(TP_RST | LCD_BL | LCD_RST | SD_CS | USB_SEL, OUTPUT);
expander->multiDigitalWrite(TP_RST | LCD_BL | LCD_RST, HIGH);
// use extend GPIO for SD card
expander->digitalWrite(SD_CS, LOW);
SPI.setHwCs(false);
#endif
SPI.begin(SD_CLK, SD_MISO, SD_MOSI, SD_SS);
if (!SD.begin()) {
Serial.println("SD Card Mount Failed");
return false;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return false;
}
Serial.print("SD Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
return true;
}
#endif
void setup()
{
esp_log_level_set(PH_TAG, ESP_LOG_DEBUG);
esp_log_level_set("httpd_uri", ESP_LOG_DEBUG);
Serial.begin(115200);
delay(10);
Serial.printf("ESP-IDF Version: %s\n", esp_get_idf_version());
#ifdef ESP_ARDUINO_VERSION_STR
Serial.printf("Arduino Version: %s\n", ESP_ARDUINO_VERSION_STR);
#else
Serial.printf("Arduino Version: %d.%d.%d\n", ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH);
#endif
Serial.printf("PsychicHttp Version: %s\n", PSYCHIC_VERSION_STR);
// We start by connecting to a WiFi network
// To debug, please enable Core Debug Level to Verbose
if (connectToWifi())
{
//Setup our NTP to get the current time.
if (connectToWifi()) {
// Setup our NTP to get the current time.
sntp_set_time_sync_notification_cb(timeAvailable);
sntp_servermode_dhcp(1); // (optional)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
esp_sntp_servermode_dhcp(1); // (optional)
#else
sntp_servermode_dhcp(1); // (optional)
#endif
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2);
//set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname)) {
// set up our esp32 to listen on the psychic.local domain
if (MDNS.begin(local_hostname))
MDNS.addService("http", "tcp", 80);
else
Serial.println("Error starting mDNS");
return;
}
MDNS.addService("http", "tcp", 80);
if(!LittleFS.begin())
{
if (!LittleFS.begin()) {
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
return;
}
//look up our keys?
#ifdef PSY_ENABLE_SSL
if (app_enable_ssl)
{
File fp = LittleFS.open("/server.crt");
if (fp)
{
server_cert = fp.readString();
#ifdef PSY_ENABLE_SSL
// look up our keys?
if (app_enable_ssl) {
File fp = LittleFS.open("/server.crt");
if (fp) {
server_cert = fp.readString();
// Serial.println("Server Cert:");
// Serial.println(server_cert);
}
else
{
Serial.println("server.pem not found, SSL not available");
app_enable_ssl = false;
}
fp.close();
File fp2 = LittleFS.open("/server.key");
if (fp2)
{
server_key = fp2.readString();
// Serial.println("Server Key:");
// Serial.println(server_key);
}
else
{
Serial.println("server.key not found, SSL not available");
app_enable_ssl = false;
}
fp2.close();
// Serial.println("Server Cert:");
// Serial.println(server_cert);
} else {
Serial.println("server.pem not found, SSL not available");
app_enable_ssl = false;
}
#endif
fp.close();
//setup server config stuff here
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
File fp2 = LittleFS.open("/server.key");
if (fp2) {
server_key = fp2.readString();
#ifdef PSY_ENABLE_SSL
server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
//do we want secure or not?
if (app_enable_ssl)
{
server.listen(443, server_cert.c_str(), server_key.c_str());
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
PsychicHttpServer *redirectServer = new PsychicHttpServer();
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
redirectServer->listen(80);
redirectServer->onNotFound([](PsychicRequest *request) {
String url = "https://" + request->host() + request->url();
return request->redirect(url.c_str());
});
// Serial.println("Server Key:");
// Serial.println(server_key);
} else {
Serial.println("server.key not found, SSL not available");
app_enable_ssl = false;
}
else
server.listen(80);
#else
server.listen(80);
#endif
fp2.close();
}
// do we want secure or not?
if (app_enable_ssl) {
server.setCertificate(server_cert.c_str(), server_key.c_str());
// this creates a 2nd server listening on port 80 and redirects all requests HTTPS
PsychicHttpServer* redirectServer = new PsychicHttpServer();
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
redirectServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) {
String url = "https://" + request->host() + request->url();
return response->redirect(url.c_str()); });
}
#endif
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
//serve static files from LittleFS/www on / only to clients on same wifi network
//this is where our /index.html file lives
// curl -i http://psychic.local/
PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/");
handler->setFilter(ON_STA_FILTER);
handler->setCacheControl("max-age=60");
loggingMiddleware.setOutput(Serial);
//serve static files from LittleFS/www-ap on / only to clients on SoftAP
//this is where our /index.html file lives
server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER);
basicAuth.setUsername(app_user);
basicAuth.setPassword(app_pass);
basicAuth.setRealm(app_name);
basicAuth.setAuthMethod(HTTPAuthMethod::BASIC_AUTH);
basicAuth.setAuthFailureMessage("You must log in.");
//serve static files from LittleFS/img on /img
//it's more efficient to serve everything from a single www directory, but this is also possible.
// curl -i http://psychic.local/img/request_flow.png
digestAuth.setUsername(app_user);
digestAuth.setPassword(app_pass);
digestAuth.setRealm(app_name);
digestAuth.setAuthMethod(HTTPAuthMethod::DIGEST_AUTH);
digestAuth.setAuthFailureMessage("You must log in.");
// corsMiddleware.setAllowCredentials(true);
// corsMiddleware.setOrigin("http://www.example.com,https://www.example.com,http://api.example.com,https://api.example.com");
// corsMiddleware.setHeaders("Origin,X-Requested-With,Content-Type,Accept,Content-Type,Authorization,X-Access-Token");
server.addMiddleware(&loggingMiddleware);
// this will send CORS headers on every HTTP_OPTIONS request that contains the Origin: header
server.addMiddleware(&corsMiddleware);
// rewrites!
server.rewrite("/rewrite", "/api?foo=rewrite");
// serve static files from LittleFS/www on / only to clients on same wifi network
// this is where our /index.html file lives
// curl -i http://psychic.local/
server.serveStatic("/", LittleFS, "/www/")
->setCacheControl("max-age=60")
->addFilter(ON_STA_FILTER);
// serve static files from LittleFS/www-ap on / only to clients on SoftAP
// this is where our /index.html file lives
server.serveStatic("/", LittleFS, "/www-ap/")->addFilter(ON_AP_FILTER);
// serve static files from LittleFS/img on /img
// it's more efficient to serve everything from a single www directory, but this is also possible.
// curl -i http://psychic.local/img/request_flow.png
server.serveStatic("/img", LittleFS, "/img/");
//you can also serve single files
// curl -i http://psychic.local/myfile.txt
#ifdef PSY_ENABLE_SDCARD
// if we detect an SD card, serve all files from sd:/ on http://psychic.local/sd
if (setupSDCard())
server.serveStatic("/sd", SD, "/");
#endif
// you can also serve single files
// curl -i http://psychic.local/myfile.txt
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
//example callback everytime a connection is opened
server.onOpen([](PsychicClient *client) {
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
});
// example callback everytime a connection is opened
server.onOpen([](PsychicClient* client) { Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); });
//example callback everytime a connection is closed
server.onClose([](PsychicClient *client) {
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
});
// example callback everytime a connection is closed
server.onClose([](PsychicClient* client) { Serial.printf("[http] connection #%u closed\n", client->socket()); });
//api - json message passed in as post body
// curl -i -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://psychic.local/api
server.on("/api", HTTP_POST, [](PsychicRequest *request, JsonVariant &json)
{
// api - json message passed in as post body
// curl -i -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://psychic.local/api
server.on("/api", HTTP_POST, [](PsychicRequest* request, PsychicResponse* resp, JsonVariant& json) {
JsonObject input = json.as<JsonObject>();
//create our response json
PsychicJsonResponse response = PsychicJsonResponse(request);
// create our response json
PsychicJsonResponse response(resp);
JsonObject output = response.getRoot();
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
output["method"] = request->methodStr();
//work with some params
if (input.containsKey("foo"))
{
// work with some params
if (input.containsKey("foo")) {
String foo = input["foo"];
output["foo"] = foo;
}
return response.send();
});
//ip - get info about the client
// curl -i http://psychic.local/ip
server.on("/ip", HTTP_GET, [](PsychicRequest *request)
{
// ip - get info about the client
// curl -i http://psychic.local/ip
server.on("/ip", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
String output = "Your IP is: " + request->client()->remoteIP().toString();
return request->reply(output.c_str());
return response->send(output.c_str());
});
//client connect/disconnect to a url
// curl -i http://psychic.local/handler
PsychicWebHandler *connectionHandler = new PsychicWebHandler();
connectionHandler->onRequest([](PsychicRequest *request)
{
return request->reply("OK");
});
connectionHandler->onOpen([](PsychicClient *client) {
Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
});
connectionHandler->onClose([](PsychicClient *client) {
Serial.printf("[handler] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
});
// client connect/disconnect to a url
// curl -i http://psychic.local/handler
PsychicWebHandler* connectionHandler = new PsychicWebHandler();
connectionHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) { return response->send("OK"); });
connectionHandler->onOpen([](PsychicClient* client) { Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); });
connectionHandler->onClose([](PsychicClient* client) { Serial.printf("[handler] connection #%u closed\n", client->socket()); });
//add it to our server
// add it to our server
server.on("/handler", connectionHandler);
//api - parameters passed in via query eg. /api?foo=bar
// curl -i 'http://psychic.local/api?foo=bar'
server.on("/api", HTTP_GET, [](PsychicRequest *request)
{
//showcase some of the variables
Serial.println(request->host());
Serial.println(request->uri());
Serial.println(request->path());
Serial.println(request->queryString());
//create a response object
//create our response json
PsychicJsonResponse response = PsychicJsonResponse(request);
// api - parameters passed in via query eg. /api?foo=bar
// curl -i 'http://psychic.local/api?foo=bar'
server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* resp) {
// create our response json
PsychicJsonResponse response = PsychicJsonResponse(resp);
JsonObject output = response.getRoot();
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
output["method"] = request->methodStr();
//work with some params
if (request->hasParam("foo"))
{
// work with some params
if (request->hasParam("foo")) {
String foo = request->getParam("foo")->value();
output["foo"] = foo;
}
@@ -355,112 +448,135 @@ void setup()
return response.send();
});
//JsonResponse example
// curl -i http://psychic.local/json
server.on("/json", HTTP_GET, [](PsychicRequest *request)
{
PsychicJsonResponse response = PsychicJsonResponse(request);
// curl -i -X GET 'http://psychic.local/any'
// curl -i -X POST 'http://psychic.local/any'
server.on("/any", HTTP_ANY, [](PsychicRequest* request, PsychicResponse* resp) {
// create our response json
PsychicJsonResponse response = PsychicJsonResponse(resp);
JsonObject output = response.getRoot();
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
output["method"] = request->methodStr();
return response.send();
});
// curl -i 'http://psychic.local/simple'
server.on("/simple", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
return response->send("Simple");
})
->setURIMatchFunction(MATCH_SIMPLE);
#ifdef PSY_ENABLE_REGEX
// curl -i 'http://psychic.local/regex/23'
// curl -i 'http://psychic.local/regex/4223'
server.on("^/regex/([\\d]+)/?$", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
// look up our regex matches
std::smatch matches;
if (request->getRegexMatches(matches)) {
String output;
output += "Matches: " + String(matches.size()) + "<br/>\n";
output += "Matched URI: " + String(matches.str(0).c_str()) + "<br/>\n";
output += "Match 1: " + String(matches.str(1).c_str()) + "<br/>\n";
return response->send(output.c_str());
} else
return response->send("No regex match.");
})
->setURIMatchFunction(MATCH_REGEX);
#endif
// JsonResponse example
// curl -i http://psychic.local/json
server.on("/json", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
PsychicJsonResponse jsonResponse = PsychicJsonResponse(response);
char key[16];
char value[32];
JsonObject root = response.getRoot();
for (int i=0; i<100; i++)
{
JsonObject root = jsonResponse.getRoot();
for (int i = 0; i < 100; i++) {
sprintf(key, "key%d", i);
sprintf(value, "value is %d", i);
root[key] = value;
}
return response.send();
});
//how to redirect a request
// curl -i http://psychic.local/redirect
server.on("/redirect", HTTP_GET, [](PsychicRequest *request)
{
return request->redirect("/alien.png");
return jsonResponse.send();
});
//how to do basic auth
// curl -i --user admin:admin http://psychic.local/auth-basic
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request)
{
if (!request->authenticate(app_user, app_pass))
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
return request->reply("Auth Basic Success!");
});
// how to redirect a request
// curl -i http://psychic.local/redirect
server.on("/redirect", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->redirect("/alien.png"); });
//how to do digest auth
// curl -i --user admin:admin http://psychic.local/auth-digest
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request)
{
if (!request->authenticate(app_user, app_pass))
return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in.");
return request->reply("Auth Digest Success!");
});
// how to do basic auth
// curl -i --user admin:admin http://psychic.local/auth-basic
server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
return response->send("Auth Basic Success!");
})->addMiddleware(&basicAuth);
//example of getting / setting cookies
// curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies
server.on("/cookies", HTTP_GET, [](PsychicRequest *request)
{
PsychicResponse response(request);
// how to do digest auth
// curl -i --user admin:admin http://psychic.local/auth-digest
server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
return response->send("Auth Digest Success!");
})->addMiddleware(&digestAuth);
// example of getting / setting cookies
// curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies
server.on("/cookies", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
int counter = 0;
if (request->hasCookie("counter"))
{
counter = std::stoi(request->getCookie("counter").c_str());
char cookie[14];
size_t size = sizeof(cookie);
if (request->getCookie("counter", cookie, &size) == ESP_OK) {
// value is null-terminated.
counter = std::stoi(cookie);
counter++;
}
sprintf(cookie, "%d", counter);
char cookie[10];
sprintf(cookie, "%i", counter);
response.setCookie("counter", cookie);
response.setContent(cookie);
return response.send();
response->setCookie("counter", cookie);
response->setContent(cookie);
return response->send();
});
//example of getting POST variables
// curl -i -d "param1=value1&param2=value2" -X POST http://psychic.local/post
server.on("/post", HTTP_POST, [](PsychicRequest *request)
{
// example of getting POST variables
// curl -i -d "param1=value1&param2=value2" -X POST http://psychic.local/post
// curl -F "param1=value1" -F "param2=value2" -X POST http://psychic.local/post
server.on("/post", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
String output;
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return request->reply(output.c_str());
return response->send(output.c_str());
});
//you can set up a custom 404 handler.
// curl -i http://psychic.local/404
server.onNotFound([](PsychicRequest *request)
{
return request->reply(404, "text/html", "Custom 404 Handler");
});
// you can set up a custom 404 handler.
// curl -i http://psychic.local/404
server.onNotFound([](PsychicRequest* request, PsychicResponse* response) { return response->send(404, "text/html", "Custom 404 Handler"); });
//handle a very basic upload as post body
PsychicUploadHandler *uploadHandler = new PsychicUploadHandler();
uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) {
// handle a very basic upload as post body
PsychicUploadHandler* uploadHandler = new PsychicUploadHandler();
uploadHandler->onUpload([](PsychicRequest* request, const String& filename, uint64_t index, uint8_t* data, size_t len, bool last) {
File file;
String path = "/www/" + filename;
Serial.printf("Writing %d/%d bytes to: %s\n", (int)index+(int)len, request->contentLength(), path.c_str());
Serial.printf("Writing %d/%d bytes to: %s\n", (int)index + (int)len, request->contentLength(), path.c_str());
if (last)
Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len);
Serial.printf("%s is finished. Total bytes: %llu\n", path.c_str(), (uint64_t)index + (uint64_t)len);
//our first call?
// our first call?
if (!index)
file = LittleFS.open(path, FILE_WRITE);
else
file = LittleFS.open(path, FILE_APPEND);
if(!file) {
if (!file) {
Serial.println("Failed to open file");
return ESP_FAIL;
}
if(!file.write(data, len)) {
if (!file.write(data, len)) {
Serial.println("Write failed");
return ESP_FAIL;
}
@@ -468,42 +584,41 @@ void setup()
return ESP_OK;
});
//gets called after upload has been handled
uploadHandler->onRequest([](PsychicRequest *request)
{
// gets called after upload has been handled
uploadHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
String url = "/" + request->getFilename();
String output = "<a href=\"" + url + "\">" + url + "</a>";
return request->reply(output.c_str());
return response->send(output.c_str());
});
//wildcard basic file upload - POST to /upload/filename.ext
// use http://psychic.local/ to test
// wildcard basic file upload - POST to /upload/filename.ext
// use http://psychic.local/ to test
server.on("/upload/*", HTTP_POST, uploadHandler);
//a little bit more complicated multipart form
PsychicUploadHandler *multipartHandler = new PsychicUploadHandler();
multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) {
// a little bit more complicated multipart form
PsychicUploadHandler* multipartHandler = new PsychicUploadHandler();
multipartHandler->onUpload([](PsychicRequest* request, const String& filename, uint64_t index, uint8_t* data, size_t len, bool last) {
File file;
String path = "/www/" + filename;
//some progress over serial.
Serial.printf("Writing %d bytes to: %s\n", (int)len, path.c_str());
// some progress over serial.
Serial.printf("Writing %d bytes to: %s @ index %llu\n", (int)len, path.c_str(), index);
if (last)
Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len);
Serial.printf("%s is finished. Total bytes: %llu\n", path.c_str(), (uint64_t)index + (uint64_t)len);
//our first call?
// our first call?
if (!index)
file = LittleFS.open(path, FILE_WRITE);
else
file = LittleFS.open(path, FILE_APPEND);
if(!file) {
if (!file) {
Serial.println("Failed to open file");
return ESP_FAIL;
}
if(!file.write(data, len)) {
if (!file.write(data, len)) {
Serial.println("Write failed");
return ESP_FAIL;
}
@@ -511,57 +626,83 @@ void setup()
return ESP_OK;
});
//gets called after upload has been handled
multipartHandler->onRequest([](PsychicRequest *request)
{
if (request->hasParam("file_upload"))
{
PsychicWebParameter *file = request->getParam("file_upload");
// gets called after upload has been handled
multipartHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
String output;
if (request->hasParam("file_upload")) {
PsychicWebParameter* file = request->getParam("file_upload");
String url = "/" + file->value();
String output;
output += "<a href=\"" + url + "\">" + url + "</a><br/>\n";
output += "Bytes: " + String(file->size()) + "<br/>\n";
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return request->reply(output.c_str());
}
else
return request->reply("No upload.");
if (request->hasParam("param1"))
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
if (request->hasParam("param2"))
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return response->send(output.c_str());
});
//wildcard basic file upload - POST to /upload/filename.ext
// use http://psychic.local/ to test
// wildcard basic file upload - POST to /upload/filename.ext
// use http://psychic.local/ to test
// just multipart data: curl -F "param1=multi" -F "param2=part" http://psychic.local/multipart
server.on("/multipart", HTTP_POST, multipartHandler);
//a websocket echo server
// npm install -g wscat
// wscat -c ws://psychic.local/ws
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
// form only multipart handler
// curl -F "param1=multi" -F "param2=part" http://psychic.local/multipart-data
PsychicUploadHandler* multipartFormHandler = new PsychicUploadHandler();
multipartFormHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
String output;
if (request->hasParam("param1"))
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
if (request->hasParam("param2"))
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return response->send(output.c_str());
});
server.on("/multipart-data", HTTP_POST, multipartFormHandler);
// a websocket echo server
// npm install -g wscat
// Plaintext: wscat -c ws://psychic.local/ws
// SSL: wscat -n -c wss://psychic.local/ws
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str());
client->sendMessage("Hello!");
});
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
// Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), String((char*)frame->payload, frame->len).c_str());
return request->reply(frame);
});
websocketHandler.onClose([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
});
websocketHandler.onClose([](PsychicWebSocketClient* client) { Serial.printf("[socket] connection #%u closed\n", client->socket()); });
server.on("/ws", &websocketHandler);
//EventSource server
// curl -i -N http://psychic.local/events
eventSource.onOpen([](PsychicEventSourceClient *client) {
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
// EventSource server
// curl -i -N http://psychic.local/events
eventSource.onOpen([](PsychicEventSourceClient* client) {
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str());
client->send("Hello user!", NULL, millis(), 1000);
});
eventSource.onClose([](PsychicEventSourceClient *client) {
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
});
eventSource.onClose([](PsychicEventSourceClient* client) { Serial.printf("[eventsource] connection #%u closed\n", client->socket()); });
server.on("/events", &eventSource);
// example of using POST data inside the filter
// works: curl -F "secret=password" http://psychic.local/post-filter
// 404: curl -F "foo=bar" http://psychic.local/post-filter
server.on("/post-filter", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
String output;
output += "Secret: " + request->getParam("secret")->value() + "<br/>\n";
return response->send(output.c_str());
})
->addFilter([](PsychicRequest* request) {
request->loadParams();
return request->hasParam("secret");
});
server.begin();
}
}
@@ -570,14 +711,21 @@ char output[60];
void loop()
{
if (millis() - lastUpdate > 2000)
{
sprintf(output, "Millis: %d\n", millis());
if (millis() - lastUpdate > 1000) {
sprintf(output, "Millis: %lu\n", millis());
websocketHandler.sendAll(output);
sprintf(output, "%d", millis());
sprintf(output, "%lu", millis());
eventSource.send(output, "millis", millis(), 0);
lastUpdate = millis();
}
// just some dev code to test that starting / stopping the server works okay.
// delay(5000);
// Serial.println("Stopping Server");
// server.stop();
// delay(5000);
// Serial.println("Starting Server");
// server.start();
}

View File

@@ -1,11 +0,0 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html