CORS works and autoreload too

This commit is contained in:
Scott Shawcroft 2022-06-22 16:44:41 -07:00
parent eeb9b7881e
commit 7543dd9af0
No known key found for this signature in database
GPG Key ID: 0DFD512649C052DA
6 changed files with 338 additions and 173 deletions

View File

@ -44,6 +44,7 @@ static const char *TAG = "socket";
STATIC socketpool_socket_obj_t *open_socket_handles[CONFIG_LWIP_MAX_SOCKETS]; STATIC socketpool_socket_obj_t *open_socket_handles[CONFIG_LWIP_MAX_SOCKETS];
void socket_user_reset(void) { void socket_user_reset(void) {
ESP_LOGW(TAG, "Reset sockets");
for (size_t i = 0; i < MP_ARRAY_SIZE(open_socket_handles); i++) { for (size_t i = 0; i < MP_ARRAY_SIZE(open_socket_handles); i++) {
if (open_socket_handles[i]) { if (open_socket_handles[i]) {
if (open_socket_handles[i]->num > 0) { if (open_socket_handles[i]->num > 0) {
@ -74,16 +75,14 @@ int socketpool_socket_accept(socketpool_socket_obj_t *self, uint8_t *ip, uint32_
uint64_t start_ticks = supervisor_ticks_ms64(); uint64_t start_ticks = supervisor_ticks_ms64();
// Allow timeouts and interrupts // Allow timeouts and interrupts
while (newsoc == -1 && while (newsoc == -1 && !timed_out) {
!timed_out &&
!mp_hal_is_interrupted()) {
if (self->timeout_ms != (uint)-1 && self->timeout_ms != 0) { if (self->timeout_ms != (uint)-1 && self->timeout_ms != 0) {
timed_out = supervisor_ticks_ms64() - start_ticks >= self->timeout_ms; timed_out = supervisor_ticks_ms64() - start_ticks >= self->timeout_ms;
} }
RUN_BACKGROUND_TASKS; RUN_BACKGROUND_TASKS;
newsoc = lwip_accept(self->num, (struct sockaddr *)&accept_addr, &socklen); newsoc = lwip_accept(self->num, (struct sockaddr *)&accept_addr, &socklen);
// In non-blocking mode, fail instead of timing out // In non-blocking mode, fail instead of timing out
if (newsoc == -1 && self->timeout_ms == 0) { if (newsoc == -1 && (self->timeout_ms == 0 || mp_hal_is_interrupted())) {
if (errno != EAGAIN) { if (errno != EAGAIN) {
ESP_LOGE(TAG, "accept failed %d", errno); ESP_LOGE(TAG, "accept failed %d", errno);
} }
@ -283,8 +282,7 @@ int socketpool_socket_recv_into(socketpool_socket_obj_t *self,
uint64_t start_ticks = supervisor_ticks_ms64(); uint64_t start_ticks = supervisor_ticks_ms64();
received = -1; received = -1;
while (received == -1 && while (received == -1 &&
!timed_out && !timed_out) {
!mp_hal_is_interrupted()) {
if (self->timeout_ms != (uint)-1 && self->timeout_ms != 0) { if (self->timeout_ms != (uint)-1 && self->timeout_ms != 0) {
timed_out = supervisor_ticks_ms64() - start_ticks >= self->timeout_ms; timed_out = supervisor_ticks_ms64() - start_ticks >= self->timeout_ms;
} }
@ -301,6 +299,14 @@ int socketpool_socket_recv_into(socketpool_socket_obj_t *self,
} }
return -MP_EAGAIN; return -MP_EAGAIN;
} }
// Check this after going through the loop once so it can make
// progress while interrupted.
if (mp_hal_is_interrupted()) {
if (received == -1) {
return -MP_EAGAIN;
}
break;
}
} }
} else { } else {
return -MP_EBADF; return -MP_EBADF;

View File

@ -168,6 +168,7 @@ void common_hal_wifi_init(bool user_initiated) {
void wifi_user_reset(void) { void wifi_user_reset(void) {
if (wifi_user_initiated) { if (wifi_user_initiated) {
ESP_LOGW(TAG, "Reset wifi");
wifi_reset(); wifi_reset();
} }
} }

View File

@ -43,7 +43,8 @@ enum {
enum { enum {
AUTORELOAD_SUSPEND_REPL = 0x1, AUTORELOAD_SUSPEND_REPL = 0x1,
AUTORELOAD_SUSPEND_BLE = 0x2, AUTORELOAD_SUSPEND_BLE = 0x2,
AUTORELOAD_SUSPEND_USB = 0x4 AUTORELOAD_SUSPEND_USB = 0x4,
AUTORELOAD_SUSPEND_WEB = 0x8
}; };
typedef struct { typedef struct {

View File

@ -35,6 +35,7 @@
#include "shared-bindings/wifi/Radio.h" #include "shared-bindings/wifi/Radio.h"
#include "shared-module/storage/__init__.h" #include "shared-module/storage/__init__.h"
#include "shared/timeutils/timeutils.h" #include "shared/timeutils/timeutils.h"
#include "supervisor/shared/reload.h"
#include "supervisor/shared/translate/translate.h" #include "supervisor/shared/translate/translate.h"
#include "supervisor/shared/web_workflow/web_workflow.h" #include "supervisor/shared/web_workflow/web_workflow.h"
#include "supervisor/usb.h" #include "supervisor/usb.h"
@ -72,10 +73,14 @@ typedef struct {
char path[256]; char path[256];
char header_key[64]; char header_key[64];
char header_value[64]; char header_value[64];
// We store the origin so we can reply back with it.
char origin[64];
size_t content_length; size_t content_length;
size_t offset; size_t offset;
uint64_t timestamp_ms;
bool redirect; bool redirect;
bool done; bool done;
bool in_progress;
bool authenticated; bool authenticated;
bool expect; bool expect;
bool json; bool json;
@ -221,6 +226,7 @@ void supervisor_start_web_workflow(void) {
pool.base.type = &socketpool_socketpool_type; pool.base.type = &socketpool_socketpool_type;
common_hal_socketpool_socketpool_construct(&pool, &common_hal_wifi_radio_obj); common_hal_socketpool_socketpool_construct(&pool, &common_hal_wifi_radio_obj);
ESP_LOGW(TAG, "Starting web workflow");
listening.base.type = &socketpool_socket_type; listening.base.type = &socketpool_socket_type;
socketpool_socket(&pool, SOCKETPOOL_AF_INET, SOCKETPOOL_SOCK_STREAM, &listening); socketpool_socket(&pool, SOCKETPOOL_AF_INET, SOCKETPOOL_SOCK_STREAM, &listening);
common_hal_socketpool_socket_settimeout(&listening, 0); common_hal_socketpool_socket_settimeout(&listening, 0);
@ -274,32 +280,27 @@ void supervisor_start_web_workflow(void) {
#endif #endif
} }
static void _send_chunk(socketpool_socket_obj_t *socket, const char *chunk) { static void _send_raw(socketpool_socket_obj_t *socket, const uint8_t *buf, int len) {
char encoded_len[sizeof(size_t) * 2 + 1]; int sent = -EAGAIN;
int len = snprintf(encoded_len, sizeof(encoded_len), "%X", strlen(chunk)); while (sent == -EAGAIN) {
int sent = socketpool_socket_send(socket, (const uint8_t *)encoded_len, len); sent = socketpool_socket_send(socket, buf, len);
}
if (sent < len) { if (sent < len) {
ESP_LOGE(TAG, "short send %d %d", sent, len); ESP_LOGE(TAG, "short send %d %d", sent, len);
} }
sent = socketpool_socket_send(socket, (const uint8_t *)"\r\n", 2);
if (sent < 2) {
ESP_LOGE(TAG, "short send %d %d", sent, 2);
}
sent = socketpool_socket_send(socket, (const uint8_t *)chunk, strlen(chunk));
if (sent < (int)strlen(chunk)) {
ESP_LOGE(TAG, "short send %d %d", sent, strlen(chunk));
}
sent = socketpool_socket_send(socket, (const uint8_t *)"\r\n", 2);
if (sent < 2) {
ESP_LOGE(TAG, "short send %d %d", sent, 2);
}
} }
static void _send_str(socketpool_socket_obj_t *socket, const char *str) { static void _send_str(socketpool_socket_obj_t *socket, const char *str) {
int sent = socketpool_socket_send(socket, (const uint8_t *)str, strlen(str)); _send_raw(socket, (const uint8_t *)str, strlen(str));
if (sent < (int)strlen(str)) { }
ESP_LOGE(TAG, "short send %d %d", sent, strlen(str));
} static void _send_chunk(socketpool_socket_obj_t *socket, const char *chunk) {
char encoded_len[sizeof(size_t) * 2 + 1];
int len = snprintf(encoded_len, sizeof(encoded_len), "%X", strlen(chunk));
_send_raw(socket, (const uint8_t *)encoded_len, len);
_send_raw(socket, (const uint8_t *)"\r\n", 2);
_send_raw(socket, (const uint8_t *)chunk, strlen(chunk));
_send_raw(socket, (const uint8_t *)"\r\n", 2);
} }
static bool _endswith(const char *str, const char *suffix) { static bool _endswith(const char *str, const char *suffix) {
@ -312,67 +313,112 @@ static bool _endswith(const char *str, const char *suffix) {
return strcmp(str + (strlen(str) - strlen(suffix)), suffix) == 0; return strcmp(str + (strlen(str) - strlen(suffix)), suffix) == 0;
} }
static void _reply_continue(socketpool_socket_obj_t *socket) { static void _cors_header(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 100 Continue\r\n\r\n"; bool origin_ok = false;
_send_str(socket, response); #if CIRCUITPY_DEBUG
origin_ok = true;
#endif
_send_str(socket, "Access-Control-Allow-Credentials: true\r\nVary: Origin\r\n");
if (origin_ok) {
_send_str(socket, "Access-Control-Allow-Origin: ");
_send_str(socket, request->origin);
_send_str(socket, "\r\n");
}
} }
static void _reply_created(socketpool_socket_obj_t *socket) { static void _reply_continue(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 100 Continue\r\n";
_send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\n");
}
static void _reply_created(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 201 Created\r\n" const char *response = "HTTP/1.1 201 Created\r\n"
"Content-Length: 0\r\n\r\n"; "Content-Length: 0\r\n";
_send_str(socket, response); _send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\n");
} }
static void _reply_no_content(socketpool_socket_obj_t *socket) { static void _reply_no_content(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 204 No Content\r\n" const char *response = "HTTP/1.1 204 No Content\r\n"
"Content-Length: 0\r\n\r\n"; "Content-Length: 0\r\n";
_send_str(socket, response); _send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\n");
} }
static void _reply_missing(socketpool_socket_obj_t *socket) { static void _reply_access_control(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 204 No Content\r\n"
"Content-Length: 0\r\n"
"Access-Control-Allow-Methods: PUT, GET, DELETE, OPTIONS\r\n"
"Access-Control-Allow-Headers: X-Timestamp, Content-Type\r\n";
_send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\n");
}
static void _reply_missing(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 404 Not Found\r\n" const char *response = "HTTP/1.1 404 Not Found\r\n"
"Content-Length: 0\r\n\r\n"; "Content-Length: 0\r\n";
_send_str(socket, response); _send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\n");
} }
static void _reply_forbidden(socketpool_socket_obj_t *socket) { static void _reply_forbidden(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 403 Forbidden\r\n" const char *response = "HTTP/1.1 403 Forbidden\r\n"
"Content-Length: 0\r\n\r\n"; "Content-Length: 0\r\n";
_send_str(socket, response); _send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\n");
} }
static void _reply_conflict(socketpool_socket_obj_t *socket) { static void _reply_conflict(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 409 Conflict\r\n" const char *response = "HTTP/1.1 409 Conflict\r\n"
"Content-Length: 19\r\n\r\nUSB storage active."; "Content-Length: 19\r\n";
_send_str(socket, response); _send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\nUSB storage active.");
} }
static void _reply_payload_too_large(socketpool_socket_obj_t *socket) { static void _reply_payload_too_large(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 413 Payload Too Large\r\n" const char *response = "HTTP/1.1 413 Payload Too Large\r\n"
"Content-Length: 0\r\n\r\n"; "Content-Length: 0\r\n";
_send_str(socket, response); _send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\n");
} }
static void _reply_expectation_failed(socketpool_socket_obj_t *socket) { static void _reply_expectation_failed(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 417 Expectation Failed\r\n" const char *response = "HTTP/1.1 417 Expectation Failed\r\n"
"Content-Length: 0\r\n\r\n"; "Content-Length: 0\r\n";
_send_str(socket, response); _send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\n");
} }
static void _reply_unauthorized(socketpool_socket_obj_t *socket) { static void _reply_unauthorized(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 401 Unauthorized\r\n" const char *response = "HTTP/1.1 401 Unauthorized\r\n"
"Content-Length: 0\r\n" "Content-Length: 0\r\n"
"WWW-Authenticate: Basic realm=\"CircuitPython\"\r\n\r\n"; "WWW-Authenticate: Basic realm=\"CircuitPython\"\r\n";
_send_str(socket, response); _send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\n");
} }
static void _reply_server_error(socketpool_socket_obj_t *socket) { static void _reply_server_error(socketpool_socket_obj_t *socket, _request *request) {
const char *response = "HTTP/1.1 500 Internal Server Error\r\n" const char *response = "HTTP/1.1 500 Internal Server Error\r\n"
"Content-Length: 0\r\n\r\n"; "Content-Length: 0\r\n";
_send_str(socket, response); _send_str(socket, response);
_cors_header(socket, request);
_send_str(socket, "\r\n");
} }
static void _reply_redirect(socketpool_socket_obj_t *socket, const char *path) { static void _reply_redirect(socketpool_socket_obj_t *socket, _request *request, const char *path) {
const char *redirect_response = "HTTP/1.1 301 Moved Permanently\r\nConnection: close\r\nContent-Length: 0\r\nLocation: http://"; const char *redirect_response = "HTTP/1.1 301 Moved Permanently\r\nConnection: close\r\nContent-Length: 0\r\nLocation: http://";
int nodelay = 1; int nodelay = 1;
lwip_setsockopt(socket->num, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); lwip_setsockopt(socket->num, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
@ -380,13 +426,17 @@ static void _reply_redirect(socketpool_socket_obj_t *socket, const char *path) {
_send_str(socket, common_hal_mdns_server_get_hostname(&mdns)); _send_str(socket, common_hal_mdns_server_get_hostname(&mdns));
_send_str(socket, ".local"); _send_str(socket, ".local");
_send_str(socket, path); _send_str(socket, path);
_send_str(socket, "\r\n\r\n"); _send_str(socket, "\r\n");
_cors_header(socket, request);
_send_str(socket, "\r\n");
} }
static void _reply_directory_json(socketpool_socket_obj_t *socket, FF_DIR *dir, const char *request_path, const char *path) { static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *request, FF_DIR *dir, const char *request_path, const char *path) {
const char *ok_response = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" const char *ok_response = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n"
"Content-Type: application/json\r\n\r\n"; "Content-Type: application/json\r\n";
socketpool_socket_send(socket, (const uint8_t *)ok_response, strlen(ok_response)); socketpool_socket_send(socket, (const uint8_t *)ok_response, strlen(ok_response));
_cors_header(socket, request);
_send_str(socket, "\r\n");
_send_chunk(socket, "["); _send_chunk(socket, "[");
bool first = true; bool first = true;
@ -436,76 +486,23 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, FF_DIR *dir,
_send_chunk(socket, ""); _send_chunk(socket, "");
} }
static void _reply_directory_html(socketpool_socket_obj_t *socket, FF_DIR *dir, const char *request_path, const char *path) { static void _reply_directory_html(socketpool_socket_obj_t *socket, _request *request, FF_DIR *dir, const char *request_path, const char *path) {
const char *ok_response = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" const char *ok_response = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n"
"Content-Type: text/html\r\n\r\n"; "Content-Type: text/html\r\n";
socketpool_socket_send(socket, (const uint8_t *)ok_response, strlen(ok_response)); socketpool_socket_send(socket, (const uint8_t *)ok_response, strlen(ok_response));
_send_chunk(socket, "<!DOCTYPE html><html><head><title>"); _cors_header(socket, request);
_send_chunk(socket, path); _send_str(socket, "\r\n");
_send_chunk(socket, "</title><meta charset=\"UTF-8\"></head>"); _send_chunk(socket, "<!DOCTYPE html><html><head><title></title><meta charset=\"UTF-8\"></head>");
_send_chunk(socket, "<script src=\"http://127.0.0.1:8000/circuitpython.js\" defer=true></script>"); _send_chunk(socket, "<script src=\"http://127.0.0.1:8000/circuitpython.js\" defer=true></script>");
_send_chunk(socket, "<body><h1>"); _send_chunk(socket, "<body><h1></h1><template id=\"row\"><tr><td></td><td></td><td><a></a></td><td></td><td><button class=\"delete\">🗑️</button></td></tr></template><table><thead><tr><th>Type</th><th>Size</th><th>Path</th><th>Modified</th><th></th></tr></thead><tbody>");
_send_chunk(socket, path); _send_chunk(socket, "</tbody></table><hr><input type=\"file\" id=\"files\" multiple><button type=\"submit\" id=\"upload\">Upload</button>");
_send_chunk(socket, "</h1><pre>");
if (strlen(path) > 1) {
_send_chunk(socket, "🗀\t<a href=\"..\">..</a><br/>");
}
FILINFO file_info; _send_chunk(socket, "<hr>+🗀&nbsp;<input type=\"text\" id=\"name\"><button type=\"submit\" id=\"mkdir\">Create Directory</button>");
char *fn = file_info.fname;
FRESULT res = f_readdir(dir, &file_info);
while (res == FR_OK && fn[0] != 0) {
// uint64_t truncated_time = timeutils_mktime(1980 + (file_info.fdate >> 9),
// (file_info.fdate >> 5) & 0xf,
// file_info.fdate & 0x1f,
// file_info.ftime >> 11,
// (file_info.ftime >> 5) & 0x1f,
// (file_info.ftime & 0x1f) * 2) * 1000000000ULL;
// entry->truncated_time = truncated_time;
// if ((file_info.fattrib & AM_DIR) != 0) {
// entry->flags = 1; // Directory
// entry->file_size = 0;
// } else {
// entry->flags = 0;
// entry->file_size = file_info.fsize;
// }
// _send_chunk(socket, "<li>");
if ((file_info.fattrib & AM_DIR) != 0) {
_send_chunk(socket, "🗀\t");
} else if (_endswith(file_info.fname, ".txt") ||
_endswith(file_info.fname, ".py") ||
_endswith(file_info.fname, ".js") ||
_endswith(file_info.fname, ".json")) {
_send_chunk(socket, "🖹\t");
} else if (_endswith(file_info.fname, ".html")) {
_send_chunk(socket, "🌐\t");
} else {
_send_chunk(socket, "\t");
}
_send_chunk(socket, "<a href=\"");
_send_chunk(socket, request_path);
_send_chunk(socket, file_info.fname);
if ((file_info.fattrib & AM_DIR) != 0) {
_send_chunk(socket, "/");
}
_send_chunk(socket, "\">");
_send_chunk(socket, file_info.fname);
_send_chunk(socket, "</a>");
_send_chunk(socket, "<button value=\"");
_send_chunk(socket, file_info.fname);
_send_chunk(socket, "\" class=\"delete\">🗑️</button><br/>");
res = f_readdir(dir, &file_info);
}
_send_chunk(socket, "</pre><hr><input type=\"file\" id=\"files\" multiple><button type=\"submit\" id=\"upload\">Upload</button>");
_send_chunk(socket, "<hr>+🗀 <input type=\"text\" id=\"name\"><button type=\"submit\" id=\"mkdir\">Create Directory</button>");
_send_chunk(socket, "</body></html>"); _send_chunk(socket, "</body></html>");
_send_chunk(socket, ""); _send_chunk(socket, "");
} }
static void _reply_with_file(socketpool_socket_obj_t *socket, const char *filename, FIL *active_file) { static void _reply_with_file(socketpool_socket_obj_t *socket, _request *request, const char *filename, FIL *active_file) {
uint32_t total_length = f_size(active_file); uint32_t total_length = f_size(active_file);
char encoded_len[10]; char encoded_len[10];
snprintf(encoded_len, sizeof(encoded_len), "%d", total_length); snprintf(encoded_len, sizeof(encoded_len), "%d", total_length);
@ -524,6 +521,7 @@ static void _reply_with_file(socketpool_socket_obj_t *socket, const char *filena
} else { } else {
_send_str(socket, "Content-Type: application/octet-stream\r\n"); _send_str(socket, "Content-Type: application/octet-stream\r\n");
} }
_cors_header(socket, request);
_send_str(socket, "\r\n"); _send_str(socket, "\r\n");
uint32_t total_read = 0; uint32_t total_read = 0;
@ -552,12 +550,14 @@ static void _reply_with_file(socketpool_socket_obj_t *socket, const char *filena
} }
} }
static void _reply_with_devices_json(socketpool_socket_obj_t *socket) { static void _reply_with_devices_json(socketpool_socket_obj_t *socket, _request *request) {
mdns_remoteservice_obj_t found_devices[32]; mdns_remoteservice_obj_t found_devices[32];
size_t total_results = mdns_server_find(&mdns, "_circuitpython", "_tcp", 1, found_devices, MP_ARRAY_SIZE(found_devices)); size_t total_results = mdns_server_find(&mdns, "_circuitpython", "_tcp", 1, found_devices, MP_ARRAY_SIZE(found_devices));
size_t count = MIN(total_results, MP_ARRAY_SIZE(found_devices)); size_t count = MIN(total_results, MP_ARRAY_SIZE(found_devices));
const char *ok_response = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: application/json\r\n\r\n"; const char *ok_response = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: application/json\r\n";
socketpool_socket_send(socket, (const uint8_t *)ok_response, strlen(ok_response)); socketpool_socket_send(socket, (const uint8_t *)ok_response, strlen(ok_response));
_cors_header(socket, request);
_send_str(socket, "\r\n");
_send_chunk(socket, "{\"total\": "); _send_chunk(socket, "{\"total\": ");
char total_encoded[4]; char total_encoded[4];
snprintf(total_encoded, sizeof(total_encoded), "%d", total_results); snprintf(total_encoded, sizeof(total_encoded), "%d", total_results);
@ -585,9 +585,11 @@ static void _reply_with_devices_json(socketpool_socket_obj_t *socket) {
_send_chunk(socket, ""); _send_chunk(socket, "");
} }
static void _reply_with_version_json(socketpool_socket_obj_t *socket) { static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request *request) {
const char *ok_response = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: application/json\r\n\r\n"; const char *ok_response = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: application/json\r\n";
socketpool_socket_send(socket, (const uint8_t *)ok_response, strlen(ok_response)); socketpool_socket_send(socket, (const uint8_t *)ok_response, strlen(ok_response));
_cors_header(socket, request);
_send_str(socket, "\r\n");
_send_chunk(socket, "{\"web_api_version\": 1, \"version\": \""); _send_chunk(socket, "{\"web_api_version\": 1, \"version\": \"");
_send_chunk(socket, MICROPY_GIT_TAG); _send_chunk(socket, MICROPY_GIT_TAG);
_send_chunk(socket, "\", \"build_date\": \""); _send_chunk(socket, "\", \"build_date\": \"");
@ -658,7 +660,7 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req
FIL active_file; FIL active_file;
if (_usb_active()) { if (_usb_active()) {
_reply_conflict(socket); _reply_conflict(socket, request);
return; return;
} }
FRESULT result = f_open(fs, &active_file, path, FA_WRITE); FRESULT result = f_open(fs, &active_file, path, FA_WRITE);
@ -670,9 +672,9 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req
if (result != FR_OK) { if (result != FR_OK) {
ESP_LOGW(TAG, "file write error %d %s", result, path); ESP_LOGW(TAG, "file write error %d %s", result, path);
_reply_server_error(socket); _reply_server_error(socket, request);
} else if (request->expect) { } else if (request->expect) {
_reply_continue(socket); _reply_continue(socket, request);
} }
// Change the file size to start. // Change the file size to start.
@ -684,9 +686,9 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req
#endif #endif
// Too large. // Too large.
if (request->expect) { if (request->expect) {
_reply_expectation_failed(socket); _reply_expectation_failed(socket, request);
} else { } else {
_reply_payload_too_large(socket); _reply_payload_too_large(socket, request);
} }
return; return;
@ -722,22 +724,22 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req
usb_msc_unlock(); usb_msc_unlock();
#endif #endif
if (new_file) { if (new_file) {
_reply_created(socket); _reply_created(socket, request);
} else { } else {
_reply_no_content(socket); _reply_no_content(socket, request);
} }
} }
static void _reply(socketpool_socket_obj_t *socket, _request *request) { static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
if (request->redirect) { if (request->redirect) {
_reply_redirect(socket, request->path); _reply_redirect(socket, request, request->path);
} else if (memcmp(request->path, "/fs/", 4) == 0) { } else if (memcmp(request->path, "/fs/", 4) == 0) {
if (!request->authenticated) { if (!request->authenticated) {
ESP_LOGW(TAG, "not authed"); ESP_LOGW(TAG, "not authed");
if (_api_password[0] != '\0') { if (_api_password[0] != '\0') {
_reply_unauthorized(socket); _reply_unauthorized(socket, request);
} else { } else {
_reply_forbidden(socket); _reply_forbidden(socket, request);
} }
} else { } else {
ESP_LOGW(TAG, "filesystem %s %s", request->method, request->path + 3); ESP_LOGW(TAG, "filesystem %s %s", request->method, request->path + 3);
@ -755,10 +757,12 @@ static void _reply(socketpool_socket_obj_t *socket, _request *request) {
} }
// Delete is almost identical for files and directories so share the // Delete is almost identical for files and directories so share the
// implementation. // implementation.
if (strcmp(request->method, "DELETE") == 0) { if (strcmp(request->method, "OPTIONS") == 0) {
_reply_access_control(socket, request);
} else if (strcmp(request->method, "DELETE") == 0) {
if (_usb_active()) { if (_usb_active()) {
_reply_conflict(socket); _reply_conflict(socket, request);
return; return false;
} }
FILINFO file; FILINFO file;
@ -773,12 +777,13 @@ static void _reply(socketpool_socket_obj_t *socket, _request *request) {
} }
if (result == FR_NO_PATH || result == FR_NO_FILE) { if (result == FR_NO_PATH || result == FR_NO_FILE) {
_reply_missing(socket); _reply_missing(socket, request);
} else if (result != FR_OK) { } else if (result != FR_OK) {
ESP_LOGW(TAG, "rm error %d %s", result, path); ESP_LOGW(TAG, "rm error %d %s", result, path);
_reply_server_error(socket); _reply_server_error(socket, request);
} else { } else {
_reply_no_content(socket); _reply_no_content(socket, request);
return true;
} }
} else if (directory) { } else if (directory) {
if (strcmp(request->method, "GET") == 0) { if (strcmp(request->method, "GET") == 0) {
@ -790,20 +795,20 @@ static void _reply(socketpool_socket_obj_t *socket, _request *request) {
} }
if (res != FR_OK) { if (res != FR_OK) {
ESP_LOGW(TAG, "unable to open %d %s", res, path); ESP_LOGW(TAG, "unable to open %d %s", res, path);
_reply_missing(socket); _reply_missing(socket, request);
return; return false;
} }
if (request->json) { if (request->json) {
_reply_directory_json(socket, &dir, request->path, path); _reply_directory_json(socket, request, &dir, request->path, path);
} else { } else {
_reply_directory_html(socket, &dir, request->path, path); _reply_directory_html(socket, request, &dir, request->path, path);
} }
f_closedir(&dir); f_closedir(&dir);
} else if (strcmp(request->method, "PUT") == 0) { } else if (strcmp(request->method, "PUT") == 0) {
if (_usb_active()) { if (_usb_active()) {
_reply_conflict(socket); _reply_conflict(socket, request);
return; return false;
} }
FRESULT result = f_mkdir(fs, path); FRESULT result = f_mkdir(fs, path);
@ -811,14 +816,15 @@ static void _reply(socketpool_socket_obj_t *socket, _request *request) {
usb_msc_unlock(); usb_msc_unlock();
#endif #endif
if (result == FR_EXIST) { if (result == FR_EXIST) {
_reply_no_content(socket); _reply_no_content(socket, request);
} else if (result == FR_NO_PATH) { } else if (result == FR_NO_PATH) {
_reply_missing(socket); _reply_missing(socket, request);
} else if (result != FR_OK) { } else if (result != FR_OK) {
ESP_LOGE(TAG, "mkdir error %d %s", result, path); ESP_LOGE(TAG, "mkdir error %d %s", result, path);
_reply_server_error(socket); _reply_server_error(socket, request);
} else { } else {
_reply_created(socket); _reply_created(socket, request);
return true;
} }
} }
} else { // Dealing with a file. } else { // Dealing with a file.
@ -828,14 +834,15 @@ static void _reply(socketpool_socket_obj_t *socket, _request *request) {
if (result != FR_OK) { if (result != FR_OK) {
// TODO: 404 // TODO: 404
_reply_missing(socket); _reply_missing(socket, request);
} else { } else {
_reply_with_file(socket, path, &active_file); _reply_with_file(socket, request, path, &active_file);
} }
f_close(&active_file); f_close(&active_file);
} else if (strcmp(request->method, "PUT") == 0) { } else if (strcmp(request->method, "PUT") == 0) {
_write_file_and_reply(socket, request, fs, path); _write_file_and_reply(socket, request, fs, path);
return true;
} }
} }
} }
@ -846,15 +853,16 @@ static void _reply(socketpool_socket_obj_t *socket, _request *request) {
// Return error if not a GET // Return error if not a GET
} }
if (strcmp(path, "/devices.json") == 0) { if (strcmp(path, "/devices.json") == 0) {
_reply_with_devices_json(socket); _reply_with_devices_json(socket, request);
} else if (strcmp(path, "/version.json") == 0) { } else if (strcmp(path, "/version.json") == 0) {
_reply_with_version_json(socket); _reply_with_version_json(socket, request);
} }
} else { } else {
const char *ok_response = "HTTP/1.1 200 OK\r\nContent-Length: 11\r\nContent-Type: text/plain\r\n\r\nHello World"; const char *ok_response = "HTTP/1.1 200 OK\r\nContent-Length: 11\r\nContent-Type: text/plain\r\n\r\nHello World";
int sent = socketpool_socket_send(socket, (const uint8_t *)ok_response, strlen(ok_response)); int sent = socketpool_socket_send(socket, (const uint8_t *)ok_response, strlen(ok_response));
ESP_LOGW(TAG, "sent ok %d", sent); ESP_LOGW(TAG, "sent ok %d", sent);
} }
return false;
} }
static void _process_request(socketpool_socket_obj_t *socket, _request *request) { static void _process_request(socketpool_socket_obj_t *socket, _request *request) {
@ -865,12 +873,16 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request)
while (more && !error) { while (more && !error) {
int len = socketpool_socket_recv_into(socket, &c, 1); int len = socketpool_socket_recv_into(socket, &c, 1);
if (len != 1) { if (len != 1) {
if (len != -EAGAIN && len != -EBADF) { if (len != -EAGAIN && len != -EBADF && len != -EPERM) {
ESP_LOGW(TAG, "received %d", len); ESP_LOGW(TAG, "received %d", len);
} }
more = false; more = false;
break; break;
} }
if (!request->in_progress) {
autoreload_suspend(AUTORELOAD_SUSPEND_WEB);
request->in_progress = true;
}
// ESP_LOGI(TAG, "%c", c); // ESP_LOGI(TAG, "%c", c);
switch (request->state) { switch (request->state) {
case STATE_METHOD: { case STATE_METHOD: {
@ -947,6 +959,11 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request)
request->expect = strcmp(request->header_value, "100-continue") == 0; request->expect = strcmp(request->header_value, "100-continue") == 0;
} else if (strcmp(request->header_key, "Accept") == 0) { } else if (strcmp(request->header_key, "Accept") == 0) {
request->json = strcmp(request->header_value, "application/json") == 0; request->json = strcmp(request->header_value, "application/json") == 0;
} else if (strcmp(request->header_key, "Origin") == 0) {
strcpy(request->origin, request->header_value);
} else if (strcmp(request->header_key, "X-Timestamp") == 0) {
request->timestamp_ms = strtoul(request->header_value, NULL, 10);
;
} }
ESP_LOGW(TAG, "Header %s %s", request->header_key, request->header_value); ESP_LOGW(TAG, "Header %s %s", request->header_key, request->header_value);
} else if (request->offset > sizeof(request->header_value) - 1) { } else if (request->offset > sizeof(request->header_value) - 1) {
@ -974,23 +991,27 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request)
if (!request->done) { if (!request->done) {
return; return;
} }
_reply(socket, request); bool reload = _reply(socket, request);
ESP_LOGI(TAG, "request done");
request->done = false; request->done = false;
request->in_progress = false;
autoreload_resume(AUTORELOAD_SUSPEND_WEB);
if (reload) {
autoreload_trigger();
}
} }
void supervisor_web_workflow_background(void) { void supervisor_web_workflow_background(void) {
// Otherwise, see if we have another socket to accept. // Otherwise, see if we have another socket to accept.
if (!common_hal_socketpool_socket_get_connected(&active) && if ((!common_hal_socketpool_socket_get_connected(&active) || !active_request.in_progress) &&
!common_hal_socketpool_socket_get_closed(&listening) && !common_hal_socketpool_socket_get_closed(&listening) &&
listening.num > 0) { listening.num > 0) {
if (!common_hal_socketpool_socket_get_closed(&active)) {
common_hal_socketpool_socket_close(&active);
}
uint32_t ip; uint32_t ip;
uint32_t port; uint32_t port;
int newsoc = socketpool_socket_accept(&listening, (uint8_t *)&ip, &port); int newsoc = socketpool_socket_accept(&listening, (uint8_t *)&ip, &port);
if (newsoc == -EBADF) { if (newsoc == -EBADF) {
ESP_LOGW(TAG, "Closing listening socket");
common_hal_socketpool_socket_close(&listening); common_hal_socketpool_socket_close(&listening);
return; return;
} }
@ -998,6 +1019,11 @@ void supervisor_web_workflow_background(void) {
ESP_LOGI(TAG, "newsoc %d", newsoc); ESP_LOGI(TAG, "newsoc %d", newsoc);
} }
if (newsoc > 0) { if (newsoc > 0) {
// Close the active socket because we have another we accepted.
if (!common_hal_socketpool_socket_get_closed(&active)) {
ESP_LOGW(TAG, "Closing active socket");
common_hal_socketpool_socket_close(&active);
}
ESP_LOGE(TAG, "Accepted socket %d", newsoc); ESP_LOGE(TAG, "Accepted socket %d", newsoc);
// TODO: Don't do this because it uses the private struct layout. // TODO: Don't do this because it uses the private struct layout.
// Create the socket // Create the socket
@ -1005,13 +1031,15 @@ void supervisor_web_workflow_background(void) {
active.pool = &pool; active.pool = &pool;
active.connected = true; active.connected = true;
common_hal_socketpool_socket_settimeout(&active, 0); common_hal_socketpool_socket_settimeout(&active, 0);
active_request.state = STATE_METHOD; active_request.state = STATE_METHOD;
active_request.offset = 0; active_request.offset = 0;
active_request.done = false; active_request.done = false;
active_request.in_progress = false;
active_request.redirect = false; active_request.redirect = false;
active_request.origin[0] = '\0';
active_request.timestamp_ms = 0;
lwip_fcntl(newsoc, F_SETFL, O_NONBLOCK); lwip_fcntl(newsoc, F_SETFL, O_NONBLOCK);
} }

View File

@ -1,27 +1,155 @@
console.log("Hello world");
let new_directory_name = document.getElementById("name"); let new_directory_name = document.getElementById("name");
let files = document.getElementById("files"); let files = document.getElementById("files");
var url_base = window.location;
var current_path;
function mkdir(e) { async function refresh_list() {
console.log("mkdir"); current_path = window.location.hash.substr(1);
console.log(e); if (current_path == "") {
current_path = "/";
}
const response = await fetch(new URL("/fs" + current_path, url_base),
{
headers: {
"Accept": "application/json"
},
credentials: "include"
}
);
const data = await response.json();
console.log(data);
var new_children = [];
var template = document.querySelector('#row');
if (window.location.path != "/fs/") {
var clone = template.content.cloneNode(true);
var td = clone.querySelectorAll("td");
td[0].textContent = "🗀";
var path = clone.querySelector("a");
let parent = new URL("..", "file://" + current_path);
path.href = "#" + parent.pathname;
path.textContent = "..";
// Remove the delete button
td[4].replaceChildren();
new_children.push(clone);
}
for (const f of data) {
// Clone the new row and insert it into the table
var clone = template.content.cloneNode(true);
var td = clone.querySelectorAll("td");
var icon = "⬇";
var file_path = current_path + f.name;
let api_url = new URL("/fs" + file_path, url_base);
if (f.directory) {
file_path = "#" + file_path + "/";
api_url += "/";
} else {
file_path = api_url;
}
if (f.directory) {
icon = "🗀";
} else if(f.name.endsWith(".txt") ||
f.name.endsWith(".py") ||
f.name.endsWith(".js") ||
f.name.endsWith(".json")) {
icon = "🖹";
} else if (f.name.endsWith(".html")) {
icon = "🌐";
}
td[0].textContent = icon;
td[1].textContent = f.file_size;
var path = clone.querySelector("a");
path.href = file_path;
path.textContent = f.name;
td[3].textContent = (new Date(f.modified_ns / 1000000)).toLocaleString();
var delete_button = clone.querySelector("button.delete");
delete_button.value = api_url;
delete_button.onclick = del;
new_children.push(clone);
}
var tbody = document.querySelector("tbody");
tbody.replaceChildren(...new_children);
} }
function upload(e) { async function find_devices() {
console.log("upload"); const response = await fetch("http://circuitpython.local/cp/devices.json");
console.log(files.files); let url = new URL("/", response.url);
console.log(response, url);
url_base = url.href;
const data = await response.json();
console.log(data);
refresh_list();
} }
function del(e) { async function mkdir(e) {
console.log("delete"); const response = await fetch(
console.log(e); new URL("/fs" + current_path + new_directory_name.value + "/", url_base),
if (confirm("Delete " + e.target.value + " and all of its contents?")) { {
console.log("do it"); method: "PUT",
headers: {
'X-Timestamp': Date.now()
}
}
);
if (response.ok) {
refresh_list();
new_directory_name.value = "";
mkdir_button.disabled = true;
} }
} }
async function upload(e) {
console.log("upload");
for (const file of files.files) {
console.log(file);
let file_path = new URL("/fs" + current_path + file.name, url_base);
const response = await fetch(file_path,
{
method: "PUT",
headers: {
'Content-Type': 'application/octet-stream',
'X-Timestamp': file.lastModified
},
body: file
}
)
if (response.ok) {
refresh_list();
console.log(files);
files.value = "";
upload_button.disabled = true;
}
}
}
async function del(e) {
console.log("delete");
console.log(e);
let fn = new URL(e.target.value);
var prompt = "Delete " + fn.pathname.substr(3);
if (e.target.value.endsWith("/")) {
prompt += " and all of its contents?";
} else {
prompt += "?";
}
if (confirm(prompt)) {
const response = await fetch(e.target.value,
{
method: "DELETE"
}
)
if (response.ok) {
refresh_list();
}
}
}
find_devices();
let mkdir_button = document.getElementById("mkdir"); let mkdir_button = document.getElementById("mkdir");
mkdir_button.onclick = mkdir; mkdir_button.onclick = mkdir;
@ -40,7 +168,4 @@ new_directory_name.oninput = () => {
mkdir_button.disabled = new_directory_name.value.length == 0; mkdir_button.disabled = new_directory_name.value.length == 0;
} }
const collection = document.getElementsByClassName("delete"); window.onhashchange = refresh_list;
for (let i = 0; i < collection.length; i++) {
collection[i].onclick = del;
}

4
tools/directory.html Normal file
View File

@ -0,0 +1,4 @@
<!DOCTYPE html><html><head><title></title><meta charset="UTF-8"></head>
<script src="http://127.0.0.1:8000/circuitpython.js" defer=true></script>
<body><h1></h1><template id="row"><tr><td></td><td></td><td><a></a></td><td></td><td><button class="delete">🗑️</button></td></tr></template><table><thead><tr><th>Type</th><th>Size</th><th>Path</th><th>Modified</th><th></th></tr></thead><tbody></tbody></table><hr><input type="file" id="files" multiple><button type="submit" id="upload">Upload</button><hr>+🗀&nbsp;<input type="text" id="name"><button type="submit" id="mkdir">Create Directory</button>
</body></html>