main/task_http.c

changeset 0
b74b0e4902c3
child 1
ad2c8b13eb88
equal deleted inserted replaced
-1:000000000000 0:b74b0e4902c3
1 /**
2 * @file task_http.c
3 * @brief HTTP and Websocket server functions.
4 * This uses some ESP32 Websocket code written by Blake Felt - blake.w.felt@gmail.com
5 */
6 #include "config.h"
7 #include "mbedtls/base64.h"
8 #include "mbedtls/sha1.h"
9
10
11 const char *TAG = "task_http";
12 static QueueHandle_t client_queue;
13 const static int client_queue_size = 10;
14 static TaskHandle_t xTaskHTTP = NULL;
15 static TaskHandle_t xTaskQueue = NULL;
16
17
18
19 void dump_buf(char *buf, uint16_t buflen);
20
21
22 /**
23 * @brief Send HTTP error and message
24 * @param conn The socket to send to.
25 * @param error The HTTP error code.
26 * @param message Yhe human readable explanation.
27 * @paeam body The human readable explanation.
28 */
29 static void http_error(struct netconn *conn, int error, char *message, char *body)
30 {
31 char *tmp = malloc(200);
32
33 ESP_LOGI(TAG, "Http response %d - %s", error, message);
34 if (strlen(body)) {
35 snprintf(tmp, 199, "HTTP/1.1 %d %s\r\nContent-type: text/plain\r\n\r\n%s\n", error, message, body);
36 } else {
37 snprintf(tmp, 199, "HTTP/1.1 %d %s\r\n\r\n", error, message);
38 }
39 netconn_write(conn, tmp, strlen(tmp), NETCONN_NOCOPY);
40 free(tmp);
41 }
42
43
44
45 /**
46 * @brief Send HTTP file from the filesystem.
47 * @param conn The network connection.
48 * @param url The requested url.
49 * @param mod_since The date/time of the file, or NULL.
50 * @param ipstr The IP address of the remote.
51 */
52 static void http_sendfile(struct netconn *conn, char *url, char *mod_since, char *ipstr)
53 {
54 char temp_url[128], temp_url_gz[128], header[128], c_type[32];
55 struct stat st;
56 off_t filesize;
57 size_t sentsize;
58 char strftime_buf[64];
59 err_t err;
60 bool send_gz;
61 FILE *f;
62
63 if (url[strlen(url) - 1] == '/') {
64 // If no filename given, use index.html
65 sprintf(temp_url, "/spiffs/w%sindex.html", url);
66 } else if (strncmp(url, "/log/", 5) == 0) {
67 // Logfiles are on the SD card.
68 sprintf(temp_url, "/sdcard/w%s", url);
69 } else {
70 sprintf(temp_url, "/spiffs/w%s", url);
71 for (int i = 0; i < 127; i++) {
72 if (temp_url[i] == '?')
73 temp_url[i] = '\0';
74 if (temp_url[i] == '\0')
75 break;
76 }
77 }
78 sprintf(temp_url_gz, "%s.gz", temp_url);
79
80 /*
81 * Get filesize and date for the response headers.
82 * Note that the /spiffs filesystem doesn't support file timestamps
83 * and returns the epoch for each file. Therefore we cannot use
84 * the cache based on timestamps.
85 */
86 filesize = 0;
87 strftime_buf[0] = '\0';
88 send_gz = false;
89 if (stat(temp_url_gz, &st) == 0) {
90 filesize = st.st_size;
91 strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime)));
92 send_gz = true;
93 } else if (stat(temp_url, &st) == 0) {
94 filesize = st.st_size;
95 strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime)));
96 }
97
98 /*
99 * If we have a If-Modified-Since parameter, compare that with the current
100 * filedate on disk. If It's the same send a 304 response.
101 * Cannot work on /spiffs.
102 */
103 #if 0
104 time_t Now;
105 struct tm timeInfo;
106 if (mod_since && strlen(strftime_buf)) {
107 time(&Now);
108 localtime_r(&Now, &timeInfo);
109 strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", &timeInfo);
110 sprintf(header, "HTTP/1.1 304 Not Modified\r\nDate: %s\r\n\r\n", strftime_buf);
111 netconn_write(conn, header, strlen(header), NETCONN_NOCOPY);
112 ESP_LOGI(TAG, "http_sendfile %s Not Modified, ok", temp_url);
113 return;
114 }
115 #endif
116
117 if (send_gz) {
118 f = fopen(temp_url_gz, "r");
119 } else {
120 f = fopen(temp_url, "r");
121 }
122 if (f == NULL) {
123 ESP_LOGI(TAG, "%s url \'%s\' file \'%s\' not found", ipstr, url, temp_url);
124 http_error(conn, 404, "Not found", "Not found");
125 return;
126 }
127
128 if (strcmp(".html", &temp_url[strlen(temp_url) - 5]) == 0) {
129 sprintf(c_type, "text/html");
130 } else if (strcmp(".css", &temp_url[strlen(temp_url) - 4]) == 0) {
131 sprintf(c_type, "text/css");
132 } else if (strcmp(".js", &temp_url[strlen(temp_url) - 3]) == 0) {
133 sprintf(c_type, "text/javascript");
134 } else if (strcmp(".json", &temp_url[strlen(temp_url) - 5]) == 0) {
135 sprintf(c_type, "text/json");
136 } else if (strcmp(".gz", &temp_url[strlen(temp_url) - 3]) == 0) {
137 sprintf(c_type, "application/x-gzip");
138 } else if (strcmp(".png", &temp_url[strlen(temp_url) - 4]) == 0) {
139 sprintf(c_type, "image/png");
140 } else if (strcmp(".svg", &temp_url[strlen(temp_url) - 4]) == 0) {
141 sprintf(c_type, "image/svg+xml");
142 } else if (strcmp(".oga", &temp_url[strlen(temp_url) - 4]) == 0) {
143 sprintf(c_type, "audio/ogg");
144 } else if (strcmp(".ico", &temp_url[strlen(temp_url) - 4]) == 0) {
145 sprintf(c_type, "image/x-icon");
146 } else if (strcmp(".xml", &temp_url[strlen(temp_url) - 4]) == 0) {
147 sprintf(c_type, "text/xml");
148 } else {
149 sprintf(c_type, "application/octet-stream");
150 printf("Unknown content type for %s\n", temp_url);
151 }
152
153 vTaskDelay(2 / portTICK_PERIOD_MS);
154 // httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate");
155 if (filesize) {
156 sprintf(header, "HTTP/1.1 200 OK\r\nLast-Modified: %s\r\nContent-Length: %ld\r\nContent-type: %s\r\n", strftime_buf, filesize, c_type);
157 } else {
158 sprintf(header, "HTTP/1.1 200 OK\r\nContent-type: %s\r\n", c_type);
159 }
160 if (send_gz) {
161 sprintf(header, "%sContent-Encoding: gzip\r\n", header);
162 }
163 sprintf(header, "%s\r\n", header); // Add last empty line.
164 err = netconn_write(conn, header, strlen(header), NETCONN_NOCOPY);
165 if (err != ERR_OK) {
166 ESP_LOGE(TAG, "%s sendfile %s%s err=%d on header write", ipstr, temp_url, (send_gz) ? ".gz":"", err);
167 fclose(f);
168 return;
169 }
170 // if (strstr(acceptEncodingBuffer, "gzip") == NULL)
171 // http_error(conn, 501, "Not implemented", "Your browser does not accept gzip-compressed data.");
172
173 sentsize = 0;
174 uint8_t *buff = malloc(1024);
175 size_t bytes;
176 int pause = 0;
177
178 for (;;) {
179 bytes = fread(buff, 1, 1024, f);
180 if (bytes == 0)
181 break;
182
183 err = netconn_write(conn, buff, bytes, NETCONN_NOCOPY);
184 if (err != ERR_OK) {
185 ESP_LOGE(TAG, "%s sendfile %s%s err=%d send %u bytes of %ld bytes", ipstr, temp_url, (send_gz) ? ".gz":"", err, sentsize, filesize);
186 break;
187 }
188 vTaskDelay(2 / portTICK_PERIOD_MS);
189 sentsize += bytes;
190 pause++;
191 if (pause > 50) { // 50 K
192 pause = 0;
193 vTaskDelay(50 / portTICK_PERIOD_MS);
194 }
195 }
196 fclose(f);
197 free(buff);
198
199 if (sentsize == filesize) {
200 ESP_LOGI(TAG, "%s sendfile %s%s sent %u bytes, ok (%s)", ipstr, temp_url, (send_gz) ? ".gz":"", sentsize, url);
201 }
202 }
203
204
205
206 /**
207 * @brief Handle VNC websocket events.
208 */
209 void websockify_callback(uint8_t num, WEBSOCKET_TYPE_t type, char* msg, uint64_t len)
210 {
211 switch(type) {
212 case WEBSOCKET_CONNECT:
213 ESP_LOGI(TAG,"Websockify client %i connected!",num);
214 break;
215
216 case WEBSOCKET_DISCONNECT_EXTERNAL:
217 ESP_LOGI(TAG,"Websockify client %i sent a disconnect message",num);
218 VncStopWS(num);
219 break;
220
221 case WEBSOCKET_DISCONNECT_INTERNAL:
222 ESP_LOGI(TAG,"Websockify client %i was disconnected",num);
223 VncStopWS(num);
224 break;
225
226 case WEBSOCKET_DISCONNECT_ERROR:
227 ESP_LOGI(TAG,"Websockify client %i was disconnected due to an error",num);
228 VncStopWS(num);
229 break;
230
231 case WEBSOCKET_TEXT:
232 ESP_LOGI(TAG,"Websockiify client %i sent text message of size %i:\n%s",num,(uint32_t)len,msg);
233 break;
234
235 case WEBSOCKET_BIN:
236 VncGetWSmessage(msg, len);
237 //dump_buf(msg, len);
238 break;
239
240 case WEBSOCKET_PING:
241 ESP_LOGI(TAG,"client %i pinged us with message of size %i:\n%s",num,(uint32_t)len,msg);
242 break;
243
244 case WEBSOCKET_PONG:
245 ESP_LOGI(TAG,"client %i responded to the ping",num);
246 break;
247 }
248 }
249
250
251
252 /**
253 * @brief Handle web ui websocket events.
254 */
255 void websock_callback(uint8_t num, WEBSOCKET_TYPE_t type, char* msg, uint64_t len)
256 {
257 switch(type) {
258 case WEBSOCKET_CONNECT:
259 ESP_LOGI(TAG,"Websocket client %i connected!",num);
260 break;
261
262 case WEBSOCKET_DISCONNECT_EXTERNAL:
263 ESP_LOGI(TAG,"Websocket client %i sent a disconnect message",num);
264 break;
265
266 case WEBSOCKET_DISCONNECT_INTERNAL:
267 ESP_LOGI(TAG,"Websocket client %i was disconnected",num);
268 break;
269
270 case WEBSOCKET_DISCONNECT_ERROR:
271 ESP_LOGI(TAG,"Websocket client %i was disconnected due to an error",num);
272 break;
273
274 case WEBSOCKET_TEXT:
275 ESP_LOGI(TAG,"Websocket client %i sent text message of size %i:\n%s",num,(uint32_t)len,msg);
276 dump_buf(msg, len);
277 break;
278
279 case WEBSOCKET_BIN:
280 ESP_LOGI(TAG,"Websocket client %i sent bin message of size %i:\n",num,(uint32_t)len);
281 dump_buf(msg, len);
282 break;
283
284 case WEBSOCKET_PING:
285 ESP_LOGI(TAG,"client %i pinged us with message of size %i:\n%s",num,(uint32_t)len,msg);
286 break;
287
288 case WEBSOCKET_PONG:
289 ESP_LOGI(TAG,"client %i responded to the ping",num);
290 break;
291 }
292 }
293
294
295
296 void dump_buf(char *buf, uint16_t buflen)
297 {
298 int i = 0, l = 1;
299
300 printf("request length %d\n00: ", buflen);
301 for (;;) {
302 if (i >= buflen)
303 break;
304 if ((buf[i] < ' ') || (buf[i] > 126)) {
305 if (buf[i] == '\n') {
306 printf("\\n\n%02d: ", l);
307 l++;
308 } else if (buf[i] == '\r') {
309 printf("\\r");
310 } else {
311 printf("\\%02x", buf[i]);
312 }
313 } else {
314 printf("%c", buf[i]);
315 }
316 i++;
317 }
318 printf("\n");
319 }
320
321
322
323 /**
324 * @brief Serve any client.
325 * @param http_client_sock The socket on which the client is connected.
326 */
327 static void http_serve(struct netconn *conn)
328 {
329 char ipstr[IPADDR_STRLEN_MAX];
330 struct netbuf *inbuf;
331 static char *buf;
332 static uint16_t buflen;
333 static err_t err;
334 char url[128], *p, *mod_since = NULL;
335 ip_addr_t remote_addr;
336 uint16_t remote_port;
337
338 if (netconn_getaddr(conn, &remote_addr, &remote_port, 0) == ERR_OK) {
339 strcpy(ipstr, ip4addr_ntoa(ip_2_ip4(&remote_addr)));
340 } else {
341 ipstr[0] = '\0';
342 }
343
344 netconn_set_recvtimeout(conn,1000); // allow a connection timeout of 1 second
345 err = netconn_recv(conn, &inbuf);
346
347 if (err != ERR_OK) {
348 if (err != ERR_TIMEOUT) { // Ignore timeout
349 ESP_LOGI(TAG,"%s error %d on read", ipstr, err);
350 }
351 netconn_close(conn);
352 netconn_delete(conn);
353 netbuf_delete(inbuf);
354 return;
355 }
356
357 netbuf_data(inbuf, (void**)&buf, &buflen);
358
359 if (buf) {
360 /*
361 * Build url string from the data buffer.
362 * It looks like: GET /app/localization.js HTTP/1.1
363 */
364 for (int i = 0; i < 10; i++) {
365 if (buf[i] == ' ') {
366 i++;
367 for (int j = i; j < 128; j++) {
368 url[j-i] = buf[j];
369 if (url[j-i] == ' ') {
370 url[j-i] = '\0';
371 break;
372 }
373 }
374 break;
375 }
376 }
377
378 if (strstr(buf, "GET /logfiles.json")) {
379 char temp[64];
380 FILE *dest = fopen("/spiffs/w/logfiles.json", "w");
381 if (dest) {
382 fprintf(dest, "{\"Dir\":[{\"Folder\":\"/log\",\"Files\":[");
383 DIR *dir = opendir("/sdcard/w/log");
384 if (dir) {
385 struct dirent* de = readdir(dir);
386 struct stat st;
387 bool comma = false;
388 while (de) {
389 sprintf(temp, "/sdcard/w/log/%s", de->d_name);
390 if (stat(temp, &st) == ESP_OK) {
391 fprintf(dest, "%s{\"File\":\"%s\",\"Size\":%ld,\"Date\":%ld}", (comma)?",":"", de->d_name, st.st_size, st.st_mtime);
392 comma = true;
393 }
394 de = readdir(dir);
395 vTaskDelay(5 / portTICK_PERIOD_MS);
396 }
397 closedir(dir);
398 }
399 fprintf(dest, "]}]}");
400 fclose(dest);
401 }
402 http_sendfile(conn, "/logfiles.json", NULL, ipstr);
403 netconn_close(conn);
404 netconn_delete(conn);
405 netbuf_delete(inbuf);
406 unlink("/spiffs/w/logfiles.json");
407 return;
408 }
409
410 // http requests
411 if (! strstr(buf,"Upgrade: websocket")) { // Not websocket
412 p = strstr(buf, "If-Modified-Since:"); // May be cached
413 if (p) {
414 size_t mod_len = strcspn(p, " ");
415 p += (int)(mod_len + 1);
416 mod_len = strcspn(p, "\r\n");
417 mod_since = malloc(mod_len + 2);
418 memcpy(mod_since, p, mod_len);
419 mod_since[mod_len] = '\0';
420 }
421 http_sendfile(conn, url, mod_since, ipstr);
422 if (mod_since)
423 free(mod_since);
424 mod_since = NULL;
425 netconn_close(conn);
426 netconn_delete(conn);
427 netbuf_delete(inbuf);
428 return;
429 }
430
431 // websocket for noVNC.
432 if ((strstr(buf,"GET /websockify ") && strstr(buf,"Upgrade: websocket"))) {
433 int nr = ws_server_add_client_protocol(conn, buf, buflen, "/websockify", "binary", websockify_callback);
434 ESP_LOGI(TAG, "%s new websocket on /websockify client: %d", ipstr, nr);
435 netbuf_delete(inbuf);
436 VncStartWS(nr);
437 return;
438 }
439
440 // websocket for web UI.
441 if ((strstr(buf,"GET /ws ") && strstr(buf,"Upgrade: websocket"))) {
442 int nr = ws_server_add_client_protocol(conn, buf, buflen, "/ws", "binary", websock_callback);
443 ESP_LOGI(TAG, "%s new websocket on /ws client: %d", ipstr, nr);
444 netbuf_delete(inbuf);
445 TFTstartWS(nr);
446 // Startup something? Init webscreen?
447 return;
448 }
449
450 dump_buf(buf, buflen);
451
452 if (strstr(buf, "GET /")) {
453 ESP_LOGI(TAG, "%s request: %s", ipstr, buf);
454 http_error(conn, 404, "Not found", "Not found");
455 } else {
456 http_error(conn, 405, "Invalid method", "Invalid method");
457 }
458 }
459
460 netconn_close(conn);
461 netconn_delete(conn);
462 netbuf_delete(inbuf);
463 }
464
465
466
467 /**
468 * @brief Handles clients when they first connect. passes to a queue
469 */
470 static void task_HTTPserver(void* pvParameters)
471 {
472 struct netconn *conn, *newconn;
473 static err_t err;
474
475 ESP_LOGI(TAG, "Starting http server_task");
476 client_queue = xQueueCreate(client_queue_size,sizeof(struct netconn*));
477
478 conn = netconn_new(NETCONN_TCP);
479 netconn_bind(conn,NULL,80);
480 netconn_listen(conn);
481
482 do {
483 err = netconn_accept(conn, &newconn);
484 if (err == ERR_OK) {
485 if (xQueueSendToBack(client_queue,&newconn,portMAX_DELAY) != pdTRUE) {
486 ESP_LOGE(TAG, "xQueueSendToBack() queue full");
487 };
488 }
489 vTaskDelay(5 / portTICK_PERIOD_MS);
490 } while (err == ERR_OK);
491
492 ESP_LOGE(TAG, "Stopping http server_task");
493 netconn_close(conn);
494 netconn_delete(conn);
495 vTaskDelete(NULL);
496 }
497
498
499
500 /**
501 * @brief Receives clients from queue and handle them.
502 */
503 static void task_Queue(void* pvParameters)
504 {
505 struct netconn* conn;
506
507 ESP_LOGI(TAG, "Starting Queue task");
508 for(;;) {
509 xQueueReceive(client_queue, &conn, portMAX_DELAY);
510 if (!conn)
511 continue;
512 http_serve(conn);
513 vTaskDelay(2 / portTICK_PERIOD_MS);
514 }
515 ESP_LOGE(TAG, "Stopping Queue task");
516 vTaskDelete(NULL);
517 }
518
519
520
521 void start_http_websocket(void)
522 {
523 ESP_LOGI(TAG, "Starting HTTP/Websocket server");
524
525 ws_server_start();
526 xTaskCreate(&task_HTTPserver, "HTTPserver", 3000, NULL, 9, &xTaskHTTP);
527 xTaskCreate(&task_Queue, "Queue", 4000, NULL, 6, &xTaskQueue);
528 }
529

mercurial