]> granicus.if.org Git - esp-idf/commitdiff
esp_http_server improvements to allow adding transport layer encryption
authorOndřej Hruška <ondra@ondrovo.com>
Wed, 31 Oct 2018 21:59:57 +0000 (22:59 +0100)
committerAnurag Kar <anurag.kar@espressif.com>
Sun, 3 Feb 2019 16:02:07 +0000 (21:32 +0530)
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

components/esp_http_server/include/esp_http_server.h
components/esp_http_server/src/esp_httpd_priv.h
components/esp_http_server/src/httpd_main.c
components/esp_http_server/src/httpd_sess.c
components/esp_http_server/src/httpd_txrx.c

index 4880c66acd8025dab4c1f774e9c1d2cd8cf86823..519538f9ab325b745a65165e3fab2bb497b3d688 100644 (file)
 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
  *
index ff619e39e98dcd4309544a1c37970ae7e0f94b99..5ae18dfe512a5cb9a25cf7d4855283c447316b59 100644 (file)
@@ -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
  * @}
index c4bcf68d777390d1aa163b2f2eb62a4aae83017d..dc4cbd86ea5e9b876ef69c899b81932a0b854ca0 100644 (file)
@@ -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"));
index b3561aab35675eb305cf9a0b7bd5ca305f1210ae..b53c4f9281534e60da13997dfa07228dcc5f914d 100644 (file)
@@ -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);
 }
 
index 5ab80295d2330bcd73ae0fba89dd2d4d6a38f081..d7ab0339a6dd5f7c8422e440c155d4b6e0dc6a4c 100644 (file)
 
 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;
     }