Merge branch 'bugfix/httpd_ws_sock_type' into 'master'

https_server: Added WSS server example and some http(s)+ws updates

Closes IDFGH-3822, IDFGH-3668, IDFGH-3766, and IDFGH-3444

See merge request espressif/esp-idf!10262
This commit is contained in:
David Čermák
2020-09-09 20:57:11 +08:00
29 changed files with 841 additions and 70 deletions

View File

@@ -406,6 +406,12 @@ typedef struct httpd_uri {
* If this flag is true, then method must be HTTP_GET. Otherwise the handshake will not be handled.
*/
bool is_websocket;
/**
* Flag indicating that control frames (PING, PONG, CLOSE) are also passed to the handler
* This is used if a custom processing of the control frames is needed
*/
bool handle_ws_control_frames;
#endif
} httpd_uri_t;
@@ -1466,6 +1472,20 @@ esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd);
*/
esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd);
/**
* @brief Returns list of current socket descriptors of active sessions
*
* @param[in] handle Handle to server returned by httpd_start
* @param[in,out] fds In: Number of fds allocated in the supplied structure client_fds
* Out: Number of valid client fds returned in client_fds,
* @param[out] client_fds Array of client fds
*
* @return
* - ESP_OK : Successfully retrieved session list
* - ESP_ERR_INVALID_ARG : Wrong arguments or list is longer than allocated
*/
esp_err_t httpd_get_client_list(httpd_handle_t handle, size_t *fds, int *client_fds);
/** End of Session
* @}
*/
@@ -1526,6 +1546,15 @@ typedef enum {
HTTPD_WS_TYPE_PONG = 0xA
} httpd_ws_type_t;
/**
* @brief Enum for client info description
*/
typedef enum {
HTTPD_WS_CLIENT_INVALID = 0x0,
HTTPD_WS_CLIENT_HTTP = 0x1,
HTTPD_WS_CLIENT_WEBSOCKET = 0x2,
} httpd_ws_client_info_t;
/**
* @brief WebSocket frame format
*/
@@ -1586,6 +1615,19 @@ esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *pkt);
*/
esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame);
/**
* @brief Checks the supplied socket descriptor if it belongs to any active client
* of this server instance and if the websoket protocol is active
*
* @param[in] hd Server instance data
* @param[in] fd Socket descriptor
* @return
* - HTTPD_WS_CLIENT_INVALID : This fd is not a client of this httpd
* - HTTPD_WS_CLIENT_HTTP : This fd is an active client, protocol is not WS
* - HTTPD_WS_CLIENT_WEBSOCKET : This fd is an active client, protocol is WS
*/
httpd_ws_client_info_t httpd_ws_get_fd_info(httpd_handle_t hd, int fd);
#endif /* CONFIG_HTTPD_WS_SUPPORT */
/** End of WebSocket related stuff
* @}

View File

@@ -75,6 +75,7 @@ struct sock_db {
bool ws_handshake_done; /*!< True if it has done WebSocket handshake (if this socket is a valid WS) */
bool ws_close; /*!< Set to true to close the socket later (when WS Close frame received) */
esp_err_t (*ws_handler)(httpd_req_t *r); /*!< WebSocket handler, leave to null if it's not WebSocket */
bool ws_control_frames; /*!< WebSocket flag indicating that control frames should be passed to user handlers */
#endif
};

View File

@@ -104,6 +104,26 @@ esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *ar
return ESP_OK;
}
esp_err_t httpd_get_client_list(httpd_handle_t handle, size_t *fds, int *client_fds)
{
struct httpd_data *hd = (struct httpd_data *) handle;
if (hd == NULL || fds == NULL || *fds == 0 || client_fds == NULL || *fds < hd->config.max_open_sockets) {
return ESP_ERR_INVALID_ARG;
}
size_t max_fds = *fds;
*fds = 0;
for (int i = 0; i < hd->config.max_open_sockets; ++i) {
if (hd->hd_sd[i].fd != -1) {
if (*fds < max_fds) {
client_fds[(*fds)++] = hd->hd_sd[i].fd;
} else {
return ESP_ERR_INVALID_ARG;
}
}
}
return ESP_OK;
}
void *httpd_get_global_user_ctx(httpd_handle_t handle)
{
return ((struct httpd_data *)handle)->config.global_user_ctx;

View File

@@ -758,8 +758,8 @@ esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd)
sd->ws_handler != NULL ? "Yes" : "No",
sd->ws_close ? "Yes" : "No");
if (sd->ws_handshake_done && sd->ws_handler != NULL) {
ESP_LOGD(TAG, LOG_FMT("New WS request from existing socket"));
ret = httpd_ws_get_frame_type(r);
ESP_LOGD(TAG, LOG_FMT("New WS request from existing socket, ws_type=%d"), ra->ws_type);
/* Stop and return here immediately if it's a CLOSE frame */
if (ra->ws_type == HTTPD_WS_TYPE_CLOSE) {
@@ -767,13 +767,14 @@ esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd)
return ret;
}
/* Ignore PONG frame, as this is a server */
if (ra->ws_type == HTTPD_WS_TYPE_PONG) {
return ret;
/* Pass the PONG frames to the handler as well, as user app might send PINGs */
ESP_LOGD(TAG, LOG_FMT("Received PONG frame"));
}
/* Call handler if it's a non-control frame */
if (ret == ESP_OK && ra->ws_type < HTTPD_WS_TYPE_CLOSE) {
/* Call handler if it's a non-control frame (or if handler requests control frames, as well) */
if (ret == ESP_OK &&
(ra->ws_type < HTTPD_WS_TYPE_CLOSE || sd->ws_control_frames)) {
ret = sd->ws_handler(r);
}

View File

@@ -174,6 +174,7 @@ esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
hd->hd_calls[i]->user_ctx = uri_handler->user_ctx;
#ifdef CONFIG_HTTPD_WS_SUPPORT
hd->hd_calls[i]->is_websocket = uri_handler->is_websocket;
hd->hd_calls[i]->handle_ws_control_frames = uri_handler->handle_ws_control_frames;
#endif
ESP_LOGD(TAG, LOG_FMT("[%d] installed %s"), i, uri_handler->uri);
return ESP_OK;
@@ -322,6 +323,7 @@ esp_err_t httpd_uri(struct httpd_data *hd)
aux->sd->ws_handshake_done = true;
aux->sd->ws_handler = uri->handler;
aux->sd->ws_control_frames = uri->handle_ws_control_frames;
/* Return immediately after handshake, no need to call handler here */
return ESP_OK;

View File

@@ -383,4 +383,15 @@ esp_err_t httpd_ws_get_frame_type(httpd_req_t *req)
return ESP_OK;
}
httpd_ws_client_info_t httpd_ws_get_fd_info(httpd_handle_t hd, int fd)
{
struct sock_db *sess = httpd_sess_get(hd, fd);
if (sess == NULL) {
return HTTPD_WS_CLIENT_INVALID;
}
bool is_active_ws = sess->ws_handshake_done && (!sess->ws_close);
return is_active_ws ? HTTPD_WS_CLIENT_WEBSOCKET : HTTPD_WS_CLIENT_HTTP;
}
#endif /* CONFIG_HTTPD_WS_SUPPORT */

View File

@@ -20,6 +20,11 @@
const static char *TAG = "esp_https_server";
typedef struct httpd_ssl_ctx {
esp_tls_cfg_server_t *tls_cfg;
httpd_open_func_t open_fn;
} httpd_ssl_ctx_t;
/**
* SSL socket close handler
*
@@ -93,7 +98,7 @@ static esp_err_t httpd_ssl_open(httpd_handle_t server, int sockfd)
assert(server != NULL);
// Retrieve the SSL context from the global context field (set in config)
esp_tls_cfg_server_t *global_ctx = httpd_get_global_transport_ctx(server);
httpd_ssl_ctx_t *global_ctx = httpd_get_global_transport_ctx(server);
assert(global_ctx != NULL);
esp_tls_t *tls = (esp_tls_t *)calloc(1, sizeof(esp_tls_t));
@@ -101,7 +106,7 @@ static esp_err_t httpd_ssl_open(httpd_handle_t server, int sockfd)
return ESP_ERR_NO_MEM;
}
ESP_LOGI(TAG, "performing session handshake");
int ret = esp_tls_server_session_create(global_ctx, sockfd, tls);
int ret = esp_tls_server_session_create(global_ctx->tls_cfg, sockfd, tls);
if (ret != 0) {
ESP_LOGE(TAG, "esp_tls_create_server_session failed");
goto fail;
@@ -119,6 +124,9 @@ static esp_err_t httpd_ssl_open(httpd_handle_t server, int sockfd)
ESP_LOGD(TAG, "Secure socket open");
if (global_ctx->open_fn) {
(global_ctx->open_fn)(server, sockfd);
}
return ESP_OK;
fail:
esp_tls_server_session_delete(tls);
@@ -133,7 +141,8 @@ fail:
static void free_secure_context(void *ctx)
{
assert(ctx != NULL);
esp_tls_cfg_server_t *cfg = (esp_tls_cfg_server_t *)ctx;
httpd_ssl_ctx_t *ssl_ctx = ctx;
esp_tls_cfg_server_t *cfg = ssl_ctx->tls_cfg;
ESP_LOGI(TAG, "Server shuts down, releasing SSL context");
if (cfg->cacert_buf) {
free((void *)cfg->cacert_buf);
@@ -145,14 +154,21 @@ static void free_secure_context(void *ctx)
free((void *)cfg->serverkey_buf);
}
free(cfg);
free(ssl_ctx);
}
static esp_tls_cfg_server_t *create_secure_context(const struct httpd_ssl_config *config)
static httpd_ssl_ctx_t *create_secure_context(const struct httpd_ssl_config *config)
{
esp_tls_cfg_server_t *cfg = (esp_tls_cfg_server_t *)calloc(1, sizeof(esp_tls_cfg_server_t));
if (!cfg) {
httpd_ssl_ctx_t *ssl_ctx = calloc(1, sizeof(httpd_ssl_ctx_t));
if (!ssl_ctx) {
return NULL;
}
esp_tls_cfg_server_t *cfg = (esp_tls_cfg_server_t *)calloc(1, sizeof(esp_tls_cfg_server_t));
if (!cfg) {
free(ssl_ctx);
return NULL;
}
ssl_ctx->tls_cfg = cfg;
/* cacert = CA which signs client cert, or client cert itself , which is mapped to client_verify_cert_pem */
if(config->client_verify_cert_pem != NULL) {
cfg->cacert_buf = (unsigned char *)malloc(config->client_verify_cert_len);
@@ -186,7 +202,7 @@ static esp_tls_cfg_server_t *create_secure_context(const struct httpd_ssl_config
memcpy((char *)cfg->serverkey_buf, config->prvtkey_pem, config->prvtkey_len);
cfg->serverkey_bytes = config->prvtkey_len;
return cfg;
return ssl_ctx;
}
/** Start the server */
@@ -199,16 +215,21 @@ esp_err_t httpd_ssl_start(httpd_handle_t *pHandle, struct httpd_ssl_config *conf
if (HTTPD_SSL_TRANSPORT_SECURE == config->transport_mode) {
esp_tls_cfg_server_t *esp_tls_cfg = create_secure_context(config);
if (!esp_tls_cfg) {
httpd_ssl_ctx_t *ssl_ctx = create_secure_context(config);
if (!ssl_ctx) {
return -1;
}
ESP_LOGD(TAG, "SSL context ready");
// set SSL specific config
config->httpd.global_transport_ctx = esp_tls_cfg;
config->httpd.global_transport_ctx = ssl_ctx;
config->httpd.global_transport_ctx_free_fn = free_secure_context;
if (config->httpd.open_fn) {
// since the httpd's open_fn is used for opening the SSL session, we save the configured
// user pointer and call it upon opening the ssl socket
ssl_ctx->open_fn = config->httpd.open_fn;
}
config->httpd.open_fn = httpd_ssl_open; // the open function configures the created SSL sessions
config->httpd.server_port = config->port_secure;