From 13a1f4ed780bb0d5841295ba45e4a8e5ca742110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Wed, 31 Oct 2018 22:59:57 +0100 Subject: [PATCH 1/3] esp_http_server improvements to allow adding transport layer encryption Changes: - renamed `httpd_free_sess_ctx_fn_t` to `httpd_free_ctx_fn_t` - added a `httpd_handle_t` argument to `httpd_send_func_t` and `httpd_recv_func_t` - internal function `httpd_sess_get()` is no longer static, as it's used in other files besides httpd_sess.c Bug fixes: - removed a trailing semicolon from `HTTPD_DEFAULT_CONFIG()` - fixed issue with failed `select()`, now it automatically closes invalid sockets instead of shutting down the entire server New features: - `httpd_resp_send()` and `httpd_resp_send_chunk()` now accept -1 as length to use `strlen()` internally - added `httpd_sess_set_ctx()` to accompany `httpd_sess_get_ctx()` - added a "transport context" to the session structure (next to user context) - added `httpd_sess_{get,set}_transport_ctx()` to work with this transport context - added "global user context" and "global transport context" stored in the server config (and then the handle); supports a user-provided free_fn - added a "pending func" to e.g. check for data in the transport layer receive buffer - added functions `httpd_set_sess_{send,recv,pending}_override()` that target a session by ID (i.e. not using a request object) - added `httpd_set_pending_override()` - added a "open_fn" and "close_fn" - functions called when creating and closing a session. These may be used to set up transport layer encryption or some other session-wide feature --- .../esp_http_server/include/esp_http_server.h | 246 +++++++++++++++++- .../esp_http_server/src/esp_httpd_priv.h | 30 ++- components/esp_http_server/src/httpd_main.c | 43 ++- components/esp_http_server/src/httpd_sess.c | 98 ++++++- components/esp_http_server/src/httpd_txrx.c | 61 ++++- 5 files changed, 441 insertions(+), 37 deletions(-) diff --git a/components/esp_http_server/include/esp_http_server.h b/components/esp_http_server/include/esp_http_server.h index 4880c66ac..519538f9a 100644 --- a/components/esp_http_server/include/esp_http_server.h +++ b/components/esp_http_server/include/esp_http_server.h @@ -27,6 +27,10 @@ extern "C" { #endif +/* +note: esp_https_server.h includes a customized copy of this +initializer that should be kept in sync +*/ #define HTTPD_DEFAULT_CONFIG() { \ .task_priority = tskIDLE_PRIORITY+5, \ .stack_size = 4096, \ @@ -39,7 +43,13 @@ extern "C" { .lru_purge_enable = false, \ .recv_wait_timeout = 5, \ .send_wait_timeout = 5, \ -}; + .global_user_ctx = NULL, \ + .global_user_ctx_free_fn = NULL, \ + .global_transport_ctx = NULL, \ + .global_transport_ctx_free_fn = NULL, \ + .open_fn = NULL, \ + .close_fn = NULL, \ +} #define ESP_ERR_HTTPD_BASE (0x8000) /*!< Starting number of HTTPD error codes */ #define ESP_ERR_HTTPD_HANDLERS_FULL (ESP_ERR_HTTPD_BASE + 1) /*!< All slots for registering URI handlers have been consumed */ @@ -70,6 +80,35 @@ typedef void* httpd_handle_t; */ typedef enum http_method httpd_method_t; +/** + * @brief Prototype for freeing context data (if any) + * @param[in] ctx : object to free + */ +typedef void (*httpd_free_ctx_fn_t)(void *ctx); + +/** + * @brief Function prototype for opening a session. + * + * Called immediately after the socket was opened to set up the send/recv functions and + * other parameters of the socket. + * + * @param[in] hd : server instance + * @param[in] sockfd : session socket file descriptor + * @return status + */ +typedef esp_err_t (*httpd_open_func_t)(httpd_handle_t hd, int sockfd); + +/** + * @brief Function prototype for closing a session. + * + * @note It's possible that the socket descriptor is invalid at this point, the function + * is called for all terminated sessions. Ensure proper handling of return codes. + * + * @param[in] hd : server instance + * @param[in] sockfd : session socket file descriptor + */ +typedef void (*httpd_close_func_t)(httpd_handle_t hd, int sockfd); + /** * @brief HTTP Server Configuration Structure * @@ -99,6 +138,55 @@ typedef struct httpd_config { bool lru_purge_enable; /*!< Purge "Least Recently Used" connection */ uint16_t recv_wait_timeout; /*!< Timeout for recv function (in seconds)*/ uint16_t send_wait_timeout; /*!< Timeout for send function (in seconds)*/ + + /** + * Global user context. + * + * This field can be used to store arbitrary user data within the server context. + * The value can be retrieved using the server handle, available e.g. in the httpd_req_t struct. + * + * When shutting down, the server frees up the user context by + * calling free() on the global_user_ctx field. If you wish to use a custom + * function for freeing the global user context, please specify that here. + */ + void * global_user_ctx; + httpd_free_ctx_fn_t global_user_ctx_free_fn; + + /** + * Global transport context. + * + * Similar to global_user_ctx, but used for session encoding or encryption (e.g. to hold the SSL context). + * It will be freed using free(), unless global_transport_ctx_free_fn is specified. + */ + void * global_transport_ctx; + httpd_free_ctx_fn_t global_transport_ctx_free_fn; + + /** + * Custom session opening callback. + * + * Called on a new session socket just after accept(), but before reading any data. + * + * This is an opportunity to set up e.g. SSL encryption using global_transport_ctx + * and the send/recv/pending session overrides. + * + * If a context needs to be maintained between these functions, store it in the session using + * httpd_sess_set_transport_ctx() and retrieve it later with httpd_sess_get_transport_ctx() + */ + httpd_open_func_t open_fn; + + /** + * Custom session closing callback. + * + * Called when a session is deleted, before freeing user and transport contexts and before + * closing the socket. This is a place for custom de-init code common to all sockets. + * + * Set the user or transport context to NULL if it was freed here, so the server does not + * try to free it again. + * + * This function is run for all terminated sessions, including sessions where the socket + * was closed by the network stack - that is, the file descriptor may not be valid anymore. + */ + httpd_close_func_t close_fn; } httpd_config_t; /** @@ -180,11 +268,6 @@ esp_err_t httpd_stop(httpd_handle_t handle); * @{ */ -/** - * @brief Function type for freeing context data (if any) - */ -typedef void (*httpd_free_sess_ctx_fn_t)(void *sess_ctx); - /* Max supported HTTP request header length */ #define HTTPD_MAX_REQ_HDR_LEN CONFIG_HTTPD_MAX_REQ_HDR_LEN @@ -232,7 +315,7 @@ typedef struct httpd_req { * calling free() on the sess_ctx member. If you wish to use a custom * function for freeing the session context, please specify that here. */ - httpd_free_sess_ctx_fn_t free_ctx; + httpd_free_ctx_fn_t free_ctx; } httpd_req_t; /** @@ -360,13 +443,18 @@ esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char* uri); * HTTPD_SOCK_ERR_ codes, which will eventually be conveyed as * return value of httpd_send() function * + * @param[in] hd : server instance + * @param[in] sockfd : session socket file descriptor + * @param[in] buf : buffer with bytes to send + * @param[in] buf_len : data size + * @param[in] flags : flags for the send() function * @return * - Bytes : The number of bytes sent successfully * - HTTPD_SOCK_ERR_INVALID : Invalid arguments * - HTTPD_SOCK_ERR_TIMEOUT : Timeout/interrupted while calling socket send() * - HTTPD_SOCK_ERR_FAIL : Unrecoverable error while calling socket send() */ -typedef int (*httpd_send_func_t)(int sockfd, const char *buf, size_t buf_len, int flags); +typedef int (*httpd_send_func_t)(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags); /** * @brief Prototype for HTTPDs low-level recv function @@ -376,6 +464,11 @@ typedef int (*httpd_send_func_t)(int sockfd, const char *buf, size_t buf_len, in * HTTPD_SOCK_ERR_ codes, which will eventually be conveyed as * return value of httpd_req_recv() function * + * @param[in] hd : server instance + * @param[in] sockfd : session socket file descriptor + * @param[in] buf : buffer with bytes to send + * @param[in] buf_len : data size + * @param[in] flags : flags for the send() function * @return * - Bytes : The number of bytes received successfully * - 0 : Buffer length parameter is zero / connection closed by peer @@ -383,7 +476,25 @@ typedef int (*httpd_send_func_t)(int sockfd, const char *buf, size_t buf_len, in * - HTTPD_SOCK_ERR_TIMEOUT : Timeout/interrupted while calling socket recv() * - HTTPD_SOCK_ERR_FAIL : Unrecoverable error while calling socket recv() */ -typedef int (*httpd_recv_func_t)(int sockfd, char *buf, size_t buf_len, int flags); +typedef int (*httpd_recv_func_t)(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags); + +/** + * @brief Prototype for HTTPDs low-level "get pending bytes" function + * + * @note User specified pending function must handle errors internally, + * depending upon the set value of errno, and return specific + * HTTPD_SOCK_ERR_ codes, which will be handled accordingly in + * the server task. + * + * @param[in] hd : server instance + * @param[in] sockfd : session socket file descriptor + * @return + * - Bytes : The number of bytes waiting to be received + * - HTTPD_SOCK_ERR_INVALID : Invalid arguments + * - HTTPD_SOCK_ERR_TIMEOUT : Timeout/interrupted while calling socket pending() + * - HTTPD_SOCK_ERR_FAIL : Unrecoverable error while calling socket pending() + */ +typedef int (*httpd_pending_func_t)(httpd_handle_t hd, int sockfd); /** End of TX / RX * @} @@ -435,6 +546,64 @@ esp_err_t httpd_set_recv_override(httpd_req_t *r, httpd_recv_func_t recv_func); */ esp_err_t httpd_set_send_override(httpd_req_t *r, httpd_send_func_t send_func); +/** + * @brief Override web server's pending function + * + * This function overrides the web server's pending function. This function is + * used to test for pending bytes in a socket. + * + * @note This API is supposed to be called only from the context of + * a URI handler where httpd_req_t* request pointer is valid. + * + * @param[in] r The request being responded to + * @param[in] pending_func The pending function to be set for this request + * + * @return + * - ESP_OK : On successfully registering override + * - ESP_ERR_INVALID_ARG : Null arguments + * - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer + */ +esp_err_t httpd_set_pending_override(httpd_req_t *r, httpd_pending_func_t pending_func); + +/** + * @brief Override web server's send function (by session FD) + * + * @see httpd_set_send_override() + * + * @param[in] hd HTTPD instance handle + * @param[in] fd session socket FD + * @param[in] send_func The send function to be set for this session + * + * @return status code + */ +esp_err_t httpd_set_sess_send_override(httpd_handle_t hd, int sockfd, httpd_send_func_t send_func); + +/** + * @brief Override web server's receive function (by session FD) + * + * @see httpd_set_recv_override() + * + * @param[in] hd HTTPD instance handle + * @param[in] fd session socket FD + * @param[in] recv_func The receive function to be set for this session + * + * @return status code + */ +esp_err_t httpd_set_sess_recv_override(httpd_handle_t hd, int sockfd, httpd_recv_func_t recv_func); + +/** + * @brief Override web server's pending function (by session FD) + * + * @see httpd_set_pending_override() + * + * @param[in] hd HTTPD instance handle + * @param[in] fd session socket FD + * @param[in] pending_func The receive function to be set for this session + * + * @return status code + */ +esp_err_t httpd_set_sess_pending_override(httpd_handle_t hd, int sockfd, httpd_pending_func_t pending_func); + /** * @brief Get the Socket Descriptor from the HTTP request * @@ -631,7 +800,7 @@ esp_err_t httpd_query_key_value(const char *qry, const char *key, char *val, siz * * @param[in] r The request being responded to * @param[in] buf Buffer from where the content is to be fetched - * @param[in] buf_len Length of the buffer + * @param[in] buf_len Length of the buffer, -1 to use strlen() * * @return * - ESP_OK : On successfully sending the response packet @@ -640,7 +809,7 @@ esp_err_t httpd_query_key_value(const char *qry, const char *key, char *val, siz * - ESP_ERR_HTTPD_RESP_SEND : Error in raw send * - ESP_ERR_HTTPD_INVALID_REQ : Invalid request */ -esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, size_t buf_len); +esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len); /** * @brief API to send one HTTP chunk @@ -670,7 +839,7 @@ esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, size_t buf_len); * * @param[in] r The request being responded to * @param[in] buf Pointer to a buffer that stores the data - * @param[in] buf_len Length of the data from the buffer that should be sent out + * @param[in] buf_len Length of the data from the buffer that should be sent out, -1 to use strlen() * * @return * - ESP_OK : On successfully sending the response packet chunk @@ -679,7 +848,7 @@ esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, size_t buf_len); * - ESP_ERR_HTTPD_RESP_SEND : Error in raw send * - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer */ -esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, size_t buf_len); +esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len); /* Some commonly used status codes */ #define HTTPD_200 "200 OK" /*!< HTTP Response 200 */ @@ -901,6 +1070,57 @@ int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len); */ void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd); +/** + * @brief Set session context by socket descriptor + * + * @param[in] handle Handle to server returned by httpd_start + * @param[in] sockfd The socket descriptor for which the context should be extracted. + * @param[in] ctx Context object to assign to the session + * @param[in] free_fn Function that should be called to free the context + */ +void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn); + +/** + * @brief Get session 'transport' context by socket descriptor + * @see httpd_sess_get_ctx() + * + * This context is used by the send/receive functions, for example to manage SSL context. + * + * @param[in] handle Handle to server returned by httpd_start + * @param[in] sockfd The socket descriptor for which the context should be extracted. + * @return + * - void* : Pointer to the transport context associated with this session + * - NULL : Empty context / Invalid handle / Invalid socket fd + */ +void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd); + +/** + * @brief Set session 'transport' context by socket descriptor + * @see httpd_sess_set_ctx() + * + * @param[in] handle Handle to server returned by httpd_start + * @param[in] sockfd The socket descriptor for which the context should be extracted. + * @param[in] ctx Transport context object to assign to the session + * @param[in] free_fn Function that should be called to free the transport context + */ +void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn); + +/** + * @brief Get HTTPD global user context (it was set in the server config struct) + * + * @param[in] handle Handle to server returned by httpd_start + * @return global user context + */ +void *httpd_get_global_user_ctx(httpd_handle_t handle); + +/** + * @brief Get HTTPD global transport context (it was set in the server config struct) + * + * @param[in] handle Handle to server returned by httpd_start + * @return global transport context + */ +void *httpd_get_global_transport_ctx(httpd_handle_t handle); + /** * @brief Trigger an httpd session close externally * diff --git a/components/esp_http_server/src/esp_httpd_priv.h b/components/esp_http_server/src/esp_httpd_priv.h index ff619e39e..5ae18dfe5 100644 --- a/components/esp_http_server/src/esp_httpd_priv.h +++ b/components/esp_http_server/src/esp_httpd_priv.h @@ -118,10 +118,13 @@ typedef enum { struct sock_db { int fd; /*!< The file descriptor for this socket */ void *ctx; /*!< A custom context for this socket */ + void *transport_ctx; /*!< A custom 'transport' context for this socket, to be used by send/recv/pending */ httpd_handle_t handle; /*!< Server handle */ - httpd_free_sess_ctx_fn_t free_ctx; /*!< Function for freeing the context */ + httpd_free_ctx_fn_t free_ctx; /*!< Function for freeing the context */ + httpd_free_ctx_fn_t free_transport_ctx; /*!< Function for freeing the 'transport' context */ httpd_send_func_t send_fn; /*!< Send function for this socket */ - httpd_recv_func_t recv_fn; /*!< Send function for this socket */ + httpd_recv_func_t recv_fn; /*!< Receive function for this socket */ + httpd_pending_func_t pending_fn; /*!< Pending function for this socket */ int64_t timestamp; /*!< Timestamp indicating when the socket was last used */ char pending_data[PARSER_BLOCK_SIZE]; /*!< Buffer for pending data to be received */ size_t pending_len; /*!< Length of pending data to be received */ @@ -169,6 +172,23 @@ struct httpd_data { * @{ */ +/** + * @brief Retrieve a session by its descriptor + * + * @param[in] hd Server instance data + * @param[in] sockfd Socket FD + * @return pointer into the socket DB, or NULL if not found + */ +struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd); + +/** + * @brief Delete sessions whose FDs have became invalid. + * This is a recovery strategy e.g. after select() fails. + * + * @param[in] hd Server instance data + */ +void httpd_sess_delete_invalid(struct httpd_data *hd); + /** * @brief Initializes an http session by resetting the sockets database. * @@ -454,6 +474,7 @@ size_t httpd_unrecv(struct httpd_req *r, const char *buf, size_t buf_len); * NEVER be called directly. The semantics of this is exactly similar to * send() of the BSD socket API. * + * @param[in] hd Server instance data * @param[in] sockfd Socket descriptor for sending data * @param[in] buf Pointer to the buffer from where the body of the response is taken * @param[in] buf_len Length of the buffer @@ -463,13 +484,14 @@ size_t httpd_unrecv(struct httpd_req *r, const char *buf, size_t buf_len); * - Length of data : if successful * - -1 : if failed (appropriate errno is set) */ -int httpd_default_send(int sockfd, const char *buf, size_t buf_len, int flags); +int httpd_default_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags); /** * @brief This is the low level default recv function of the HTTPD. This should * NEVER be called directly. The semantics of this is exactly similar to * recv() of the BSD socket API. * + * @param[in] hd Server instance data * @param[in] sockfd Socket descriptor for sending data * @param[out] buf Pointer to the buffer which will be filled with the received data * @param[in] buf_len Length of the buffer @@ -479,7 +501,7 @@ int httpd_default_send(int sockfd, const char *buf, size_t buf_len, int flags); * - Length of data : if successful * - -1 : if failed (appropriate errno is set) */ -int httpd_default_recv(int sockfd, char *buf, size_t buf_len, int flags); +int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags); /** End of Group : Send and Receive * @} diff --git a/components/esp_http_server/src/httpd_main.c b/components/esp_http_server/src/httpd_main.c index c4bcf68d7..dc4cbd86e 100644 --- a/components/esp_http_server/src/httpd_main.c +++ b/components/esp_http_server/src/httpd_main.c @@ -62,8 +62,8 @@ static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd) tv.tv_usec = 0; setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv)); - if (httpd_sess_new(hd, new_fd)) { - ESP_LOGW(TAG, LOG_FMT("no slots left for launching new session")); + if (ESP_OK != httpd_sess_new(hd, new_fd)) { + ESP_LOGW(TAG, LOG_FMT("session creation failed")); close(new_fd); return ESP_FAIL; } @@ -102,6 +102,16 @@ esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *ar return ESP_OK; } +void *httpd_get_global_user_ctx(httpd_handle_t handle) +{ + return ((struct httpd_data *)handle)->config.global_user_ctx; +} + +void *httpd_get_global_transport_ctx(httpd_handle_t handle) +{ + return ((struct httpd_data *)handle)->config.global_transport_ctx; +} + static void httpd_close_all_sessions(struct httpd_data *hd) { int fd = -1; @@ -159,11 +169,8 @@ static esp_err_t httpd_server(struct httpd_data *hd) int active_cnt = select(maxfd + 1, &read_set, NULL, NULL, NULL); if (active_cnt < 0) { ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno); - /* Assert, as it's not possible to recover from this point onwards, - * and there is no way to notify the main thread that server handle - * has become invalid */ - assert(false); - return ESP_FAIL; + httpd_sess_delete_invalid(hd); + return ESP_OK; } /* Case0: Do we have a control message? */ @@ -367,7 +374,27 @@ esp_err_t httpd_stop(httpd_handle_t handle) ESP_LOGD(TAG, LOG_FMT("sent control msg to stop server")); while (hd->hd_td.status != THREAD_STOPPED) { - httpd_os_thread_sleep(1000); + httpd_os_thread_sleep(100); + } + + /* Release global user context, if not NULL */ + if (hd->config.global_user_ctx) { + if (hd->config.global_user_ctx_free_fn) { + hd->config.global_user_ctx_free_fn(hd->config.global_user_ctx); + } else { + free(hd->config.global_user_ctx); + } + hd->config.global_user_ctx = NULL; + } + + /* Release global transport context, if not NULL */ + if (hd->config.global_transport_ctx) { + if (hd->config.global_transport_ctx_free_fn) { + hd->config.global_transport_ctx_free_fn(hd->config.global_transport_ctx); + } else { + free(hd->config.global_transport_ctx); + } + hd->config.global_transport_ctx = NULL; } ESP_LOGD(TAG, LOG_FMT("server stopped")); diff --git a/components/esp_http_server/src/httpd_sess.c b/components/esp_http_server/src/httpd_sess.c index b3561aab3..b53c4f928 100644 --- a/components/esp_http_server/src/httpd_sess.c +++ b/components/esp_http_server/src/httpd_sess.c @@ -33,7 +33,7 @@ bool httpd_is_sess_available(struct httpd_data *hd) return false; } -static struct sock_db *httpd_sess_get(struct httpd_data *hd, int newfd) +struct sock_db *httpd_sess_get(struct httpd_data *hd, int newfd) { int i; for (i = 0; i < hd->config.max_open_sockets; i++) { @@ -61,6 +61,12 @@ esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd) hd->hd_sd[i].handle = (httpd_handle_t) hd; hd->hd_sd[i].send_fn = httpd_default_send; hd->hd_sd[i].recv_fn = httpd_default_recv; + + /* Call user-defined session opening function */ + if (hd->config.open_fn) { + esp_err_t ret = hd->config.open_fn(hd, hd->hd_sd[i].fd); + if (ret != ESP_OK) return ret; + } return ESP_OK; } } @@ -74,8 +80,7 @@ void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd) return NULL; } - struct httpd_data *hd = (struct httpd_data *) handle; - struct sock_db *sd = httpd_sess_get(hd, sockfd); + struct sock_db *sd = httpd_sess_get(handle, sockfd); if (sd == NULL) { return NULL; } @@ -83,6 +88,50 @@ void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd) return sd->ctx; } +void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn) +{ + if (handle == NULL) { + return; + } + + struct sock_db *sd = httpd_sess_get(handle, sockfd); + if (sd == NULL) { + return; + } + + sd->ctx = ctx; + sd->free_ctx = free_fn; +} + +void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd) +{ + if (handle == NULL) { + return NULL; + } + + struct sock_db *sd = httpd_sess_get(handle, sockfd); + if (sd == NULL) { + return NULL; + } + + return sd->transport_ctx; +} + +void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn) +{ + if (handle == NULL) { + return; + } + + struct sock_db *sd = httpd_sess_get(handle, sockfd); + if (sd == NULL) { + return; + } + + sd->transport_ctx = ctx; + sd->free_transport_ctx = free_fn; +} + void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd) { @@ -98,6 +147,22 @@ void httpd_sess_set_descriptors(struct httpd_data *hd, } } +/** Check if a FD is valid */ +static int fd_is_valid(int fd) +{ + return fcntl(fd, F_GETFD) != -1 || errno != EBADF; +} + +void httpd_sess_delete_invalid(struct httpd_data *hd) +{ + for (int i = 0; i < hd->config.max_open_sockets; i++) { + if (hd->hd_sd[i].fd != -1 && !fd_is_valid(hd->hd_sd[i].fd)) { + ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), hd->hd_sd[i].fd); + httpd_sess_delete(hd, hd->hd_sd[i].fd); + } + } +} + int httpd_sess_delete(struct httpd_data *hd, int fd) { ESP_LOGD(TAG, LOG_FMT("fd = %d"), fd); @@ -105,7 +170,12 @@ int httpd_sess_delete(struct httpd_data *hd, int fd) int pre_sess_fd = -1; for (i = 0; i < hd->config.max_open_sockets; i++) { if (hd->hd_sd[i].fd == fd) { - hd->hd_sd[i].fd = -1; + /* global close handler */ + if (hd->config.close_fn) { + hd->config.close_fn(hd, fd); + } + + /* release 'user' context */ if (hd->hd_sd[i].ctx) { if (hd->hd_sd[i].free_ctx) { hd->hd_sd[i].free_ctx(hd->hd_sd[i].ctx); @@ -115,6 +185,20 @@ int httpd_sess_delete(struct httpd_data *hd, int fd) hd->hd_sd[i].ctx = NULL; hd->hd_sd[i].free_ctx = NULL; } + + /* release 'transport' context */ + if (hd->hd_sd[i].transport_ctx) { + if (hd->hd_sd[i].free_transport_ctx) { + hd->hd_sd[i].free_transport_ctx(hd->hd_sd[i].transport_ctx); + } else { + free(hd->hd_sd[i].transport_ctx); + } + hd->hd_sd[i].transport_ctx = NULL; + hd->hd_sd[i].free_transport_ctx = NULL; + } + + /* mark session slot as available */ + hd->hd_sd[i].fd = -1; break; } else if (hd->hd_sd[i].fd != -1) { /* Return the fd just preceding the one being @@ -142,6 +226,12 @@ bool httpd_sess_pending(struct httpd_data *hd, int fd) return ESP_FAIL; } + if (sd->pending_fn) { + // test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop) + // this should check e.g. for the SSL data buffer + if (sd->pending_fn(hd, fd) > 0) return true; + } + return (sd->pending_len != 0); } diff --git a/components/esp_http_server/src/httpd_txrx.c b/components/esp_http_server/src/httpd_txrx.c index 5ab80295d..d7ab0339a 100644 --- a/components/esp_http_server/src/httpd_txrx.c +++ b/components/esp_http_server/src/httpd_txrx.c @@ -22,6 +22,30 @@ static const char *TAG = "httpd_txrx"; +esp_err_t httpd_set_sess_send_override(httpd_handle_t hd, int sockfd, httpd_send_func_t send_func) +{ + struct sock_db *sess = httpd_sess_get(hd, sockfd); + if (!sess) return ESP_ERR_INVALID_ARG; + sess->send_fn = send_func; + return ESP_OK; +} + +esp_err_t httpd_set_sess_recv_override(httpd_handle_t hd, int sockfd, httpd_recv_func_t recv_func) +{ + struct sock_db *sess = httpd_sess_get(hd, sockfd); + if (!sess) return ESP_ERR_INVALID_ARG; + sess->recv_fn = recv_func; + return ESP_OK; +} + +esp_err_t httpd_set_sess_pending_override(httpd_handle_t hd, int sockfd, httpd_pending_func_t pending_func) +{ + struct sock_db *sess = httpd_sess_get(hd, sockfd); + if (!sess) return ESP_ERR_INVALID_ARG; + sess->pending_fn = pending_func; + return ESP_OK; +} + esp_err_t httpd_set_send_override(httpd_req_t *r, httpd_send_func_t send_func) { if (r == NULL || send_func == NULL) { @@ -52,6 +76,21 @@ esp_err_t httpd_set_recv_override(httpd_req_t *r, httpd_recv_func_t recv_func) return ESP_OK; } +esp_err_t httpd_set_pending_override(httpd_req_t *r, httpd_pending_func_t pending_func) +{ + if (r == NULL || pending_func == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (!httpd_valid_req(r)) { + return ESP_ERR_HTTPD_INVALID_REQ; + } + + struct httpd_req_aux *ra = r->aux; + ra->sd->pending_fn = pending_func; + return ESP_OK; +} + int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len) { if (r == NULL || buf == NULL) { @@ -63,7 +102,7 @@ int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len) } struct httpd_req_aux *ra = r->aux; - int ret = ra->sd->send_fn(ra->sd->fd, buf, buf_len, 0); + int ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0); if (ret < 0) { ESP_LOGD(TAG, LOG_FMT("error in send_fn")); return ret; @@ -77,7 +116,7 @@ static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) int ret; while (buf_len > 0) { - ret = ra->sd->send_fn(ra->sd->fd, buf, buf_len, 0); + ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0); if (ret < 0) { ESP_LOGD(TAG, LOG_FMT("error in send_fn")); return ESP_FAIL; @@ -125,7 +164,7 @@ int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t buf_len, bool halt_aft } /* Receive data of remaining length */ - int ret = ra->sd->recv_fn(ra->sd->fd, buf, buf_len, 0); + int ret = ra->sd->recv_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0); if (ret < 0) { ESP_LOGD(TAG, LOG_FMT("error in recv_fn")); if ((ret == HTTPD_SOCK_ERR_TIMEOUT) && (pending_len != 0)) { @@ -231,7 +270,7 @@ esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type) return ESP_OK; } -esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, size_t buf_len) +esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len) { if (r == NULL) { return ESP_ERR_INVALID_ARG; @@ -246,6 +285,8 @@ esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, size_t buf_len) const char *colon_separator = ": "; const char *cr_lf_seperator = "\r\n"; + if (buf_len == -1) buf_len = strlen(buf); + /* Request headers are no longer available */ ra->req_hdrs_count = 0; @@ -294,7 +335,7 @@ esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, size_t buf_len) return ESP_OK; } -esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, size_t buf_len) +esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len) { if (r == NULL) { return ESP_ERR_INVALID_ARG; @@ -304,6 +345,8 @@ esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, size_t buf_len) return ESP_ERR_HTTPD_INVALID_REQ; } + if (buf_len == -1) buf_len = strlen(buf); + struct httpd_req_aux *ra = r->aux; const char *httpd_chunked_hdr_str = "HTTP/1.1 %s\r\nContent-Type: %s\r\nTransfer-Encoding: chunked\r\n"; const char *colon_separator = ": "; @@ -359,7 +402,7 @@ esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, size_t buf_len) } if (buf) { - if (httpd_send_all(r, buf, buf_len) != ESP_OK) { + if (httpd_send_all(r, buf, (size_t) buf_len) != ESP_OK) { return ESP_ERR_HTTPD_RESP_SEND; } } @@ -520,8 +563,9 @@ static int httpd_sock_err(const char *ctx, int sockfd) return errval; } -int httpd_default_send(int sockfd, const char *buf, size_t buf_len, int flags) +int httpd_default_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags) { + (void)hd; if (buf == NULL) { return HTTPD_SOCK_ERR_INVALID; } @@ -533,8 +577,9 @@ int httpd_default_send(int sockfd, const char *buf, size_t buf_len, int flags) return ret; } -int httpd_default_recv(int sockfd, char *buf, size_t buf_len, int flags) +int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags) { + (void)hd; if (buf == NULL) { return HTTPD_SOCK_ERR_INVALID; } From 900dbfd1f6dda1e92b228189d7bf99e59c11be2f Mon Sep 17 00:00:00 2001 From: Anurag Kar Date: Fri, 2 Nov 2018 23:35:38 +0530 Subject: [PATCH 2/3] esp_http_server example tests : updated advanced_tests as per API changes --- .../http_server/advanced_tests/main/tests.c | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/examples/protocols/http_server/advanced_tests/main/tests.c b/examples/protocols/http_server/advanced_tests/main/tests.c index 74eb7f62c..8845585be 100644 --- a/examples/protocols/http_server/advanced_tests/main/tests.c +++ b/examples/protocols/http_server/advanced_tests/main/tests.c @@ -12,6 +12,11 @@ static const char *TAG="TESTS"; int pre_start_mem, post_stop_mem, post_stop_min_mem; bool basic_sanity = true; +struct async_resp_arg { + httpd_handle_t hd; + int fd; +}; + /********************* Basic Handlers Start *******************/ esp_err_t hello_get_handler(httpd_req_t *req) @@ -157,11 +162,13 @@ esp_err_t leftover_data_post_handler(httpd_req_t *req) return ESP_OK; } -int httpd_default_send(int sockfd, const char *buf, unsigned buf_len, int flags); +int httpd_default_send(httpd_handle_t hd, int sockfd, const char *buf, unsigned buf_len, int flags); void generate_async_resp(void *arg) { char buf[250]; - int fd = (int )arg; + struct async_resp_arg *resp_arg = (struct async_resp_arg *)arg; + httpd_handle_t hd = resp_arg->hd; + int fd = resp_arg->fd; #define HTTPD_HDR_STR "HTTP/1.1 200 OK\r\n" \ "Content-Type: text/html\r\n" \ "Content-Length: %d\r\n" @@ -171,11 +178,12 @@ void generate_async_resp(void *arg) snprintf(buf, sizeof(buf), HTTPD_HDR_STR, strlen(STR)); - httpd_default_send(fd, buf, strlen(buf), 0); + httpd_default_send(hd, fd, buf, strlen(buf), 0); /* Space for sending additional headers based on set_header */ - httpd_default_send(fd, "\r\n", strlen("\r\n"), 0); - httpd_default_send(fd, STR, strlen(STR), 0); + httpd_default_send(hd, fd, "\r\n", strlen("\r\n"), 0); + httpd_default_send(hd, fd, STR, strlen(STR), 0); #undef STR + free(arg); } esp_err_t async_get_handler(httpd_req_t *req) @@ -185,12 +193,15 @@ esp_err_t async_get_handler(httpd_req_t *req) /* Also register a HTTPD Work which sends the same data on the same * socket again */ - int fd = httpd_req_to_sockfd(req); - if (fd < 0) { + struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg)); + resp_arg->hd = req->handle; + resp_arg->fd = httpd_req_to_sockfd(req); + if (resp_arg->fd < 0) { return ESP_FAIL; } - ESP_LOGI(TAG, "Queuing work fd : %d", fd); - httpd_queue_work(req->handle, generate_async_resp, (void *)fd); + + ESP_LOGI(TAG, "Queuing work fd : %d", resp_arg->fd); + httpd_queue_work(req->handle, generate_async_resp, resp_arg); return ESP_OK; #undef STR } From 9c3262f80b3330d27790c78c82f5a627f0d4d4c3 Mon Sep 17 00:00:00 2001 From: Anurag Kar Date: Fri, 2 Nov 2018 23:55:40 +0530 Subject: [PATCH 3/3] esp_http_server : APIs renamed and context get/set implementations fixed * http_sess_set_*_override APIs are now the only ones available to set custom recv/send/pending functions * Fixed side effects to using http_sess_set/get_context inside URI handlers --- .../esp_http_server/include/esp_http_server.h | 114 +++++++----------- .../esp_http_server/src/esp_httpd_priv.h | 19 ++- components/esp_http_server/src/httpd_parse.c | 37 ++++-- components/esp_http_server/src/httpd_sess.c | 93 +++++++++----- components/esp_http_server/src/httpd_txrx.c | 63 ++-------- components/esp_http_server/src/httpd_uri.c | 2 + 6 files changed, 167 insertions(+), 161 deletions(-) diff --git a/components/esp_http_server/include/esp_http_server.h b/components/esp_http_server/include/esp_http_server.h index 519538f9a..793f3dd80 100644 --- a/components/esp_http_server/include/esp_http_server.h +++ b/components/esp_http_server/include/esp_http_server.h @@ -150,6 +150,10 @@ typedef struct httpd_config { * function for freeing the global user context, please specify that here. */ void * global_user_ctx; + + /** + * Free function for global user context + */ httpd_free_ctx_fn_t global_user_ctx_free_fn; /** @@ -159,6 +163,10 @@ typedef struct httpd_config { * It will be freed using free(), unless global_transport_ctx_free_fn is specified. */ void * global_transport_ctx; + + /** + * Free function for global transport context + */ httpd_free_ctx_fn_t global_transport_ctx_free_fn; /** @@ -509,100 +517,64 @@ typedef int (*httpd_pending_func_t)(httpd_handle_t hd, int sockfd); */ /** - * @brief Override web server's receive function + * @brief Override web server's receive function (by session FD) * * This function overrides the web server's receive function. This same function is - * used to read and parse HTTP headers as well as body. + * used to read HTTP request packets. * - * @note This API is supposed to be called only from the context of - * a URI handler where httpd_req_t* request pointer is valid. + * @note This API is supposed to be called either from the context of + * - an http session APIs where sockfd is a valid parameter + * - a URI handler where sockfd is obtained using httpd_req_to_sockfd() * - * @param[in] r The request being responded to - * @param[in] recv_func The receive function to be set for this request + * @param[in] hd HTTPD instance handle + * @param[in] sockfd Session socket FD + * @param[in] recv_func The receive function to be set for this session * * @return * - ESP_OK : On successfully registering override * - ESP_ERR_INVALID_ARG : Null arguments - * - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer */ -esp_err_t httpd_set_recv_override(httpd_req_t *r, httpd_recv_func_t recv_func); - -/** - * @brief Override web server's send function - * - * This function overrides the web server's send function. This same function is - * used to send out any response to any HTTP request. - * - * @note This API is supposed to be called only from the context of - * a URI handler where httpd_req_t* request pointer is valid. - * - * @param[in] r The request being responded to - * @param[in] send_func The send function to be set for this request - * - * @return - * - ESP_OK : On successfully registering override - * - ESP_ERR_INVALID_ARG : Null arguments - * - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer - */ -esp_err_t httpd_set_send_override(httpd_req_t *r, httpd_send_func_t send_func); - -/** - * @brief Override web server's pending function - * - * This function overrides the web server's pending function. This function is - * used to test for pending bytes in a socket. - * - * @note This API is supposed to be called only from the context of - * a URI handler where httpd_req_t* request pointer is valid. - * - * @param[in] r The request being responded to - * @param[in] pending_func The pending function to be set for this request - * - * @return - * - ESP_OK : On successfully registering override - * - ESP_ERR_INVALID_ARG : Null arguments - * - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer - */ -esp_err_t httpd_set_pending_override(httpd_req_t *r, httpd_pending_func_t pending_func); +esp_err_t httpd_sess_set_recv_override(httpd_handle_t hd, int sockfd, httpd_recv_func_t recv_func); /** * @brief Override web server's send function (by session FD) * - * @see httpd_set_send_override() + * This function overrides the web server's send function. This same function is + * used to send out any response to any HTTP request. * - * @param[in] hd HTTPD instance handle - * @param[in] fd session socket FD + * @note This API is supposed to be called either from the context of + * - an http session APIs where sockfd is a valid parameter + * - a URI handler where sockfd is obtained using httpd_req_to_sockfd() + * + * @param[in] hd HTTPD instance handle + * @param[in] sockfd Session socket FD * @param[in] send_func The send function to be set for this session * - * @return status code + * @return + * - ESP_OK : On successfully registering override + * - ESP_ERR_INVALID_ARG : Null arguments */ -esp_err_t httpd_set_sess_send_override(httpd_handle_t hd, int sockfd, httpd_send_func_t send_func); - -/** - * @brief Override web server's receive function (by session FD) - * - * @see httpd_set_recv_override() - * - * @param[in] hd HTTPD instance handle - * @param[in] fd session socket FD - * @param[in] recv_func The receive function to be set for this session - * - * @return status code - */ -esp_err_t httpd_set_sess_recv_override(httpd_handle_t hd, int sockfd, httpd_recv_func_t recv_func); +esp_err_t httpd_sess_set_send_override(httpd_handle_t hd, int sockfd, httpd_send_func_t send_func); /** * @brief Override web server's pending function (by session FD) * - * @see httpd_set_pending_override() + * This function overrides the web server's pending function. This function is + * used to test for pending bytes in a socket. * - * @param[in] hd HTTPD instance handle - * @param[in] fd session socket FD + * @note This API is supposed to be called either from the context of + * - an http session APIs where sockfd is a valid parameter + * - a URI handler where sockfd is obtained using httpd_req_to_sockfd() + * + * @param[in] hd HTTPD instance handle + * @param[in] sockfd Session socket FD * @param[in] pending_func The receive function to be set for this session * - * @return status code + * @return + * - ESP_OK : On successfully registering override + * - ESP_ERR_INVALID_ARG : Null arguments */ -esp_err_t httpd_set_sess_pending_override(httpd_handle_t hd, int sockfd, httpd_pending_func_t pending_func); +esp_err_t httpd_sess_set_pending_override(httpd_handle_t hd, int sockfd, httpd_pending_func_t pending_func); /** * @brief Get the Socket Descriptor from the HTTP request @@ -612,7 +584,7 @@ esp_err_t httpd_set_sess_pending_override(httpd_handle_t hd, int sockfd, httpd_p * This is useful when user wants to call functions that require * session socket fd, from within a URI handler, ie. : * httpd_sess_get_ctx(), - * httpd_trigger_sess_close(), + * httpd_sess_trigger_close(), * httpd_sess_update_timestamp(). * * @note This API is supposed to be called only from the context of @@ -1136,7 +1108,7 @@ void *httpd_get_global_transport_ctx(httpd_handle_t handle); * - ESP_ERR_NOT_FOUND : Socket fd not found * - ESP_ERR_INVALID_ARG : Null arguments */ -esp_err_t httpd_trigger_sess_close(httpd_handle_t handle, int sockfd); +esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd); /** * @brief Update timestamp for a given socket diff --git a/components/esp_http_server/src/esp_httpd_priv.h b/components/esp_http_server/src/esp_httpd_priv.h index 5ae18dfe5..68bc9d64d 100644 --- a/components/esp_http_server/src/esp_httpd_priv.h +++ b/components/esp_http_server/src/esp_httpd_priv.h @@ -240,6 +240,14 @@ esp_err_t httpd_sess_process(struct httpd_data *hd, int clifd); */ int httpd_sess_delete(struct httpd_data *hd, int clifd); +/** + * @brief Free session context + * + * @param[in] ctx Pointer to session context + * @param[in] free_fn Free function to call on session context + */ +void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn); + /** * @brief Add descriptors present in the socket database to an fd_set and * update the value of maxfd which are needed by the select function @@ -351,7 +359,16 @@ void httpd_unregister_all_uri_handlers(struct httpd_data *hd); * - true : if valid request * - false : otherwise */ -bool httpd_valid_req(httpd_req_t *r); +bool httpd_validate_req_ptr(httpd_req_t *r); + +/* httpd_validate_req_ptr() adds some overhead to frequently used APIs, + * and is useful mostly for debugging, so it's preferable to disable + * the check by defaut and enable it only if necessary */ +#ifdef CONFIG_HTTPD_VALIDATE_REQ +#define httpd_valid_req(r) httpd_validate_req_ptr(r) +#else +#define httpd_valid_req(r) true +#endif /** End of Group : URI Handling * @} diff --git a/components/esp_http_server/src/httpd_parse.c b/components/esp_http_server/src/httpd_parse.c index 93b5abddc..51843182d 100644 --- a/components/esp_http_server/src/httpd_parse.c +++ b/components/esp_http_server/src/httpd_parse.c @@ -552,6 +552,24 @@ static void init_req_aux(struct httpd_req_aux *ra, httpd_config_t *config) memset(ra->resp_hdrs, 0, config->max_resp_headers * sizeof(struct resp_hdr)); } +static void httpd_req_cleanup(httpd_req_t *r) +{ + struct httpd_req_aux *ra = r->aux; + + /* Retrieve session info from the request into the socket database */ + if (ra->sd->ctx != r->sess_ctx) { + /* Free previous context */ + httpd_sess_free_ctx(ra->sd->ctx, ra->sd->free_ctx); + ra->sd->ctx = r->sess_ctx; + } + ra->sd->free_ctx = r->free_ctx; + + /* Clear out the request and request_aux structures */ + ra->sd = NULL; + r->handle = NULL; + r->aux = NULL; +} + /* Function that processes incoming TCP data and * updates the http request data httpd_req_t */ @@ -563,7 +581,7 @@ esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd) r->handle = hd; r->aux = &hd->hd_req_aux; /* Associate the request to the socket */ - struct httpd_req_aux *ra = r->aux; + struct httpd_req_aux *ra = r->aux; ra->sd = sd; /* Set defaults */ ra->status = (char *)HTTPD_200; @@ -573,7 +591,11 @@ esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd) r->sess_ctx = sd->ctx; r->free_ctx = sd->free_ctx; /* Parse request */ - return httpd_parse_req(hd); + esp_err_t err = httpd_parse_req(hd); + if (err != ESP_OK) { + httpd_req_cleanup(r); + } + return err; } /* Function that resets the http request data @@ -592,6 +614,7 @@ esp_err_t httpd_req_delete(struct httpd_data *hd) int recv_len = MIN(sizeof(dummy) - 1, ra->remaining_len); int ret = httpd_req_recv(r, dummy, recv_len); if (ret < 0) { + httpd_req_cleanup(r); return ESP_FAIL; } @@ -599,20 +622,14 @@ esp_err_t httpd_req_delete(struct httpd_data *hd) ESP_LOGD(TAG, LOG_FMT("purging data : %s"), dummy); } - /* Retrieve session info from the request into the socket database */ - ra->sd->ctx = r->sess_ctx; - ra->sd->free_ctx = r->free_ctx; - - /* Clear out the request and request_aux structures */ - ra->sd = NULL; - r->aux = NULL; + httpd_req_cleanup(r); return ESP_OK; } /* Validates the request to prevent users from calling APIs, that are to * be called only inside URI handler, outside the handler context */ -bool httpd_valid_req(httpd_req_t *r) +bool httpd_validate_req_ptr(httpd_req_t *r) { if (r) { struct httpd_data *hd = (struct httpd_data *) r->handle; diff --git a/components/esp_http_server/src/httpd_sess.c b/components/esp_http_server/src/httpd_sess.c index b53c4f928..8c0601fd4 100644 --- a/components/esp_http_server/src/httpd_sess.c +++ b/components/esp_http_server/src/httpd_sess.c @@ -33,11 +33,23 @@ bool httpd_is_sess_available(struct httpd_data *hd) return false; } -struct sock_db *httpd_sess_get(struct httpd_data *hd, int newfd) +struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd) { + if (hd == NULL) { + return NULL; + } + + /* Check if called inside a request handler, and the + * session sockfd in use is same as the parameter */ + if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) { + /* Just return the pointer to the sock_db + * corresponding to the request */ + return hd->hd_req_aux.sd; + } + int i; for (i = 0; i < hd->config.max_open_sockets; i++) { - if (hd->hd_sd[i].fd == newfd) { + if (hd->hd_sd[i].fd == sockfd) { return &hd->hd_sd[i]; } } @@ -74,41 +86,71 @@ esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd) return ESP_FAIL; } +void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn) +{ + if (ctx) { + if (free_fn) { + free_fn(ctx); + } else { + free(ctx); + } + } +} + void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd) { - if (handle == NULL) { - return NULL; - } - struct sock_db *sd = httpd_sess_get(handle, sockfd); if (sd == NULL) { return NULL; } + /* Check if the function has been called from inside a + * request handler, in which case fetch the context from + * the httpd_req_t structure */ + struct httpd_data *hd = (struct httpd_data *) handle; + if (hd->hd_req_aux.sd == sd) { + return hd->hd_req.sess_ctx; + } + return sd->ctx; } void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn) { - if (handle == NULL) { - return; - } - struct sock_db *sd = httpd_sess_get(handle, sockfd); if (sd == NULL) { return; } - sd->ctx = ctx; + /* Check if the function has been called from inside a + * request handler, in which case set the context inside + * the httpd_req_t structure */ + struct httpd_data *hd = (struct httpd_data *) handle; + if (hd->hd_req_aux.sd == sd) { + if (hd->hd_req.sess_ctx != ctx) { + /* Don't free previous context if it is in sockdb + * as it will be freed inside httpd_req_cleanup() */ + if (sd->ctx != hd->hd_req.sess_ctx) { + /* Free previous context */ + httpd_sess_free_ctx(hd->hd_req.sess_ctx, hd->hd_req.free_ctx); + } + hd->hd_req.sess_ctx = ctx; + } + hd->hd_req.free_ctx = free_fn; + return; + } + + /* Else set the context inside the sock_db structure */ + if (sd->ctx != ctx) { + /* Free previous context */ + httpd_sess_free_ctx(sd->ctx, sd->free_ctx); + sd->ctx = ctx; + } sd->free_ctx = free_fn; } void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd) { - if (handle == NULL) { - return NULL; - } - struct sock_db *sd = httpd_sess_get(handle, sockfd); if (sd == NULL) { return NULL; @@ -119,16 +161,16 @@ void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd) void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn) { - if (handle == NULL) { - return; - } - struct sock_db *sd = httpd_sess_get(handle, sockfd); if (sd == NULL) { return; } - sd->transport_ctx = ctx; + if (sd->transport_ctx != ctx) { + /* Free previous transport context */ + httpd_sess_free_ctx(sd->transport_ctx, sd->free_transport_ctx); + sd->transport_ctx = ctx; + } sd->free_transport_ctx = free_fn; } @@ -296,7 +338,7 @@ esp_err_t httpd_sess_close_lru(struct httpd_data *hd) } } ESP_LOGD(TAG, LOG_FMT("fd = %d"), lru_fd); - return httpd_trigger_sess_close(hd, lru_fd); + return httpd_sess_trigger_close(hd, lru_fd); } int httpd_sess_iterate(struct httpd_data *hd, int start_fd) @@ -333,14 +375,9 @@ static void httpd_sess_close(void *arg) } } -esp_err_t httpd_trigger_sess_close(httpd_handle_t handle, int sockfd) +esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd) { - if (handle == NULL) { - return ESP_ERR_INVALID_ARG; - } - - struct httpd_data *hd = (struct httpd_data *) handle; - struct sock_db *sock_db = httpd_sess_get(hd, sockfd); + struct sock_db *sock_db = httpd_sess_get(handle, sockfd); if (sock_db) { return httpd_queue_work(handle, httpd_sess_close, sock_db); } diff --git a/components/esp_http_server/src/httpd_txrx.c b/components/esp_http_server/src/httpd_txrx.c index d7ab0339a..69a5ba004 100644 --- a/components/esp_http_server/src/httpd_txrx.c +++ b/components/esp_http_server/src/httpd_txrx.c @@ -22,75 +22,36 @@ static const char *TAG = "httpd_txrx"; -esp_err_t httpd_set_sess_send_override(httpd_handle_t hd, int sockfd, httpd_send_func_t send_func) +esp_err_t httpd_sess_set_send_override(httpd_handle_t hd, int sockfd, httpd_send_func_t send_func) { struct sock_db *sess = httpd_sess_get(hd, sockfd); - if (!sess) return ESP_ERR_INVALID_ARG; + if (!sess) { + return ESP_ERR_INVALID_ARG; + } sess->send_fn = send_func; return ESP_OK; } -esp_err_t httpd_set_sess_recv_override(httpd_handle_t hd, int sockfd, httpd_recv_func_t recv_func) +esp_err_t httpd_sess_set_recv_override(httpd_handle_t hd, int sockfd, httpd_recv_func_t recv_func) { struct sock_db *sess = httpd_sess_get(hd, sockfd); - if (!sess) return ESP_ERR_INVALID_ARG; + if (!sess) { + return ESP_ERR_INVALID_ARG; + } sess->recv_fn = recv_func; return ESP_OK; } -esp_err_t httpd_set_sess_pending_override(httpd_handle_t hd, int sockfd, httpd_pending_func_t pending_func) +esp_err_t httpd_sess_set_pending_override(httpd_handle_t hd, int sockfd, httpd_pending_func_t pending_func) { struct sock_db *sess = httpd_sess_get(hd, sockfd); - if (!sess) return ESP_ERR_INVALID_ARG; + if (!sess) { + return ESP_ERR_INVALID_ARG; + } sess->pending_fn = pending_func; return ESP_OK; } -esp_err_t httpd_set_send_override(httpd_req_t *r, httpd_send_func_t send_func) -{ - if (r == NULL || send_func == NULL) { - return ESP_ERR_INVALID_ARG; - } - - if (!httpd_valid_req(r)) { - return ESP_ERR_HTTPD_INVALID_REQ; - } - - struct httpd_req_aux *ra = r->aux; - ra->sd->send_fn = send_func; - return ESP_OK; -} - -esp_err_t httpd_set_recv_override(httpd_req_t *r, httpd_recv_func_t recv_func) -{ - if (r == NULL || recv_func == NULL) { - return ESP_ERR_INVALID_ARG; - } - - if (!httpd_valid_req(r)) { - return ESP_ERR_HTTPD_INVALID_REQ; - } - - struct httpd_req_aux *ra = r->aux; - ra->sd->recv_fn = recv_func; - return ESP_OK; -} - -esp_err_t httpd_set_pending_override(httpd_req_t *r, httpd_pending_func_t pending_func) -{ - if (r == NULL || pending_func == NULL) { - return ESP_ERR_INVALID_ARG; - } - - if (!httpd_valid_req(r)) { - return ESP_ERR_HTTPD_INVALID_REQ; - } - - struct httpd_req_aux *ra = r->aux; - ra->sd->pending_fn = pending_func; - return ESP_OK; -} - int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len) { if (r == NULL || buf == NULL) { diff --git a/components/esp_http_server/src/httpd_uri.c b/components/esp_http_server/src/httpd_uri.c index 9652c9439..e31a6108f 100644 --- a/components/esp_http_server/src/httpd_uri.c +++ b/components/esp_http_server/src/httpd_uri.c @@ -201,8 +201,10 @@ esp_err_t httpd_uri(struct httpd_data *hd) if (uri == NULL) { switch (err) { case HTTPD_404_NOT_FOUND: + ESP_LOGW(TAG, LOG_FMT("URI '%s' not found"), req->uri); return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND); case HTTPD_405_METHOD_NOT_ALLOWED: + ESP_LOGW(TAG, LOG_FMT("Method '%d' not allowed for URI '%s'"), req->method, req->uri); return httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED); default: return ESP_FAIL;