diff --git a/components/esp_http_server/include/esp_http_server.h b/components/esp_http_server/include/esp_http_server.h index 4880c66ac..793f3dd80 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,63 @@ 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; + + /** + * Free function for global user context + */ + 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; + + /** + * Free function for global transport context + */ + 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 +276,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 +323,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 +451,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 +472,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 +484,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 * @} @@ -398,42 +517,64 @@ typedef int (*httpd_recv_func_t)(int sockfd, char *buf, size_t buf_len, int flag */ /** - * @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); +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 + * @brief Override web server's send function (by session FD) * * 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. + * @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] send_func The send function to be set for this request + * @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 * - 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); +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) + * + * 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 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 + * - ESP_OK : On successfully registering override + * - ESP_ERR_INVALID_ARG : Null arguments + */ +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 @@ -443,7 +584,7 @@ esp_err_t httpd_set_send_override(httpd_req_t *r, httpd_send_func_t send_func); * 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 @@ -631,7 +772,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 +781,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 +811,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 +820,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 +1042,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 * @@ -916,7 +1108,7 @@ void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd); * - 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 ff619e39e..68bc9d64d 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. * @@ -220,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 @@ -331,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 * @} @@ -454,6 +491,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 +501,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 +518,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_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 b3561aab3..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; } -static 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]; } } @@ -61,6 +73,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; } } @@ -68,21 +86,94 @@ 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 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; } + /* 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) +{ + struct sock_db *sd = httpd_sess_get(handle, sockfd); + if (sd == NULL) { + return; + } + + /* 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) +{ + 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) +{ + struct sock_db *sd = httpd_sess_get(handle, sockfd); + if (sd == NULL) { + return; + } + + 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; +} + void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd) { @@ -98,6 +189,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 +212,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 +227,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 +268,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); } @@ -206,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) @@ -243,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 5ab80295d..69a5ba004 100644 --- a/components/esp_http_server/src/httpd_txrx.c +++ b/components/esp_http_server/src/httpd_txrx.c @@ -22,33 +22,33 @@ static const char *TAG = "httpd_txrx"; -esp_err_t httpd_set_send_override(httpd_req_t *r, 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) { - if (r == NULL || send_func == NULL) { + struct sock_db *sess = httpd_sess_get(hd, sockfd); + if (!sess) { 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; + sess->send_fn = send_func; return ESP_OK; } -esp_err_t httpd_set_recv_override(httpd_req_t *r, 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) { - if (r == NULL || recv_func == NULL) { + struct sock_db *sess = httpd_sess_get(hd, sockfd); + if (!sess) { return ESP_ERR_INVALID_ARG; } + sess->recv_fn = recv_func; + return ESP_OK; +} - if (!httpd_valid_req(r)) { - return ESP_ERR_HTTPD_INVALID_REQ; +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; } - - struct httpd_req_aux *ra = r->aux; - ra->sd->recv_fn = recv_func; + sess->pending_fn = pending_func; return ESP_OK; } @@ -63,7 +63,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 +77,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 +125,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 +231,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 +246,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 +296,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 +306,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 +363,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 +524,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 +538,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; } 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; diff --git a/components/esp_https_server/CMakeLists.txt b/components/esp_https_server/CMakeLists.txt new file mode 100644 index 000000000..dfdaa11ec --- /dev/null +++ b/components/esp_https_server/CMakeLists.txt @@ -0,0 +1,7 @@ +set(COMPONENT_ADD_INCLUDEDIRS include) +set(COMPONENT_SRCS "src/https_server.c") + +set(COMPONENT_REQUIRES esp_http_server openssl) +set(COMPONENT_PRIV_REQUIRES lwip) + +register_component() diff --git a/components/esp_https_server/component.mk b/components/esp_https_server/component.mk new file mode 100644 index 000000000..be1bffc58 --- /dev/null +++ b/components/esp_https_server/component.mk @@ -0,0 +1,2 @@ +COMPONENT_SRCDIRS := src +COMPONENT_ADD_INCLUDEDIRS := include diff --git a/components/esp_https_server/include/esp_https_server.h b/components/esp_https_server/include/esp_https_server.h new file mode 100644 index 000000000..2b7343e77 --- /dev/null +++ b/components/esp_https_server/include/esp_https_server.h @@ -0,0 +1,117 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_HTTPS_SERVER_H_ +#define _ESP_HTTPS_SERVER_H_ + +#include +#include "esp_err.h" +#include "esp_http_server.h" + +typedef enum { + HTTPD_SSL_TRANSPORT_SECURE, // SSL Enabled + HTTPD_SSL_TRANSPORT_INSECURE // SSL disabled +} httpd_ssl_transport_mode_t; + +/** + * HTTPS server config struct + * + * Please use HTTPD_SSL_CONFIG_DEFAULT() to initialize it. + */ +struct httpd_ssl_config { + /** + * Underlying HTTPD server config + * + * Parameters like task stack size and priority can be adjusted here. + */ + httpd_config_t httpd; + + /** CA certificate */ + const uint8_t *cacert_pem; + + /** CA certificate byte length */ + size_t cacert_len; + + /** Private key */ + const uint8_t *prvtkey_pem; + + /** Private key byte length */ + size_t prvtkey_len; + + /** Transport Mode (default secure) */ + httpd_ssl_transport_mode_t transport_mode; + + /** Port used when transport mode is secure (default 443) */ + uint16_t port_secure; + + /** Port used when transport mode is insecure (default 80) */ + uint16_t port_insecure; +}; + +typedef struct httpd_ssl_config httpd_ssl_config_t; + +/** + * Default config struct init + * + * (http_server default config had to be copied for customization) + * + * Notes: + * - port is set when starting the server, according to 'transport_mode' + * - one socket uses ~ 40kB RAM with SSL, we reduce the default socket count to 4 + * - SSL sockets are usually long-lived, closing LRU prevents pool exhaustion DOS + * - Stack size may need adjustments depending on the user application + */ +#define HTTPD_SSL_CONFIG_DEFAULT() { \ + .httpd = { \ + .task_priority = tskIDLE_PRIORITY+5, \ + .stack_size = 10240, \ + .server_port = 0, \ + .ctrl_port = 32768, \ + .max_open_sockets = 4, \ + .max_uri_handlers = 8, \ + .max_resp_headers = 8, \ + .backlog_conn = 5, \ + .lru_purge_enable = true, \ + .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, \ + }, \ + .transport_mode = HTTPD_SSL_TRANSPORT_SECURE, \ + .port_secure = 443, \ + .port_insecure = 80, \ +} + +/** + * Create a SSL capable HTTP server (secure mode may be disabled in config) + * + * @param[in,out] config - server config, must not be const. Does not have to stay valid after + * calling this function. + * @param[out] handle - storage for the server handle, must be a valid pointer + * @return success + */ +esp_err_t httpd_ssl_start(httpd_handle_t *handle, httpd_ssl_config_t *config); + +/** + * Stop the server. Blocks until the server is shut down. + * + * @param[in] handle + */ +void httpd_ssl_stop(httpd_handle_t handle); + +#endif // _ESP_HTTPS_SERVER_H_ diff --git a/components/esp_https_server/src/https_server.c b/components/esp_https_server/src/https_server.c new file mode 100644 index 000000000..4a0d21792 --- /dev/null +++ b/components/esp_https_server/src/https_server.c @@ -0,0 +1,220 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "esp_https_server.h" +#include "openssl/ssl.h" +#include "esp_log.h" +#include "sdkconfig.h" + +const static char *TAG = "esp_https_server"; + +/** + * SSL socket close handler + * + * @param[in] ctx - session transport context (SSL context we stored there) + */ +static void httpd_ssl_close(void *ctx) +{ + assert(ctx != NULL); + SSL_shutdown(ctx); + SSL_free(ctx); + ESP_LOGD(TAG, "Secure socket closed"); +} + +/** + * SSL socket pending-check function + * + * @param server + * @param sockfd + * @return number of pending bytes, negative on error + */ +static int httpd_ssl_pending(httpd_handle_t server, int sockfd) +{ + SSL *ssl = httpd_sess_get_transport_ctx(server, sockfd); + assert(ssl != NULL); + return SSL_pending(ssl); +} + +/** + * Receive from a SSL socket + * + * @param server + * @param sockfd + * @param buf + * @param buf_len + * @param flags + * @return bytes read, negative on error + */ +static int httpd_ssl_recv(httpd_handle_t server, int sockfd, char *buf, size_t buf_len, int flags) +{ + SSL *ssl = httpd_sess_get_transport_ctx(server, sockfd); + assert(ssl != NULL); + return SSL_read(ssl, buf, buf_len); +} + +/** + * Send to a SSL socket + * + * @param server + * @param sockfd + * @param buf + * @param buf_len + * @param flags + * @return bytes sent, negative on error + */ +static int httpd_ssl_send(httpd_handle_t server, int sockfd, const char *buf, size_t buf_len, int flags) +{ + SSL *ssl = httpd_sess_get_transport_ctx(server, sockfd); + assert(ssl != NULL); + return SSL_write(ssl, buf, buf_len); +} + +/** + * Open a SSL socket for the server. + * The fd is already open and ready to read / write raw data. + * + * @param server + * @param sockfd - raw socket fd + * @return success + */ +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) + SSL_CTX *global_ctx = httpd_get_global_transport_ctx(server); + assert(global_ctx != NULL); + + SSL *ssl = SSL_new(global_ctx); + if (NULL == ssl) { + ESP_LOGE(TAG, "SSL_new ret NULL (out of memory)"); + return ESP_ERR_NO_MEM; + } + + if (1 != SSL_set_fd(ssl, sockfd)) { + ESP_LOGE(TAG, "fail to set SSL fd"); + goto teardown; + } + + ESP_LOGD(TAG, "SSL accept"); + if (1 != SSL_accept(ssl)) { + ESP_LOGW(TAG, "fail to SSL_accept - handshake error"); + goto teardown; + } + + // Store the SSL session into the context field of the HTTPD session object + httpd_sess_set_transport_ctx(server, sockfd, ssl, httpd_ssl_close); + + // Set rx/tx/pending override functions + httpd_sess_set_send_override(server, sockfd, httpd_ssl_send); + httpd_sess_set_recv_override(server, sockfd, httpd_ssl_recv); + httpd_sess_set_pending_override(server, sockfd, httpd_ssl_pending); + + // all access should now go through SSL + + ESP_LOGD(TAG, "Secure socket open"); + + return ESP_OK; + +teardown: + SSL_free(ssl); + return ESP_FAIL; +} + +/** + * Tear down the HTTPD global transport context + * + * @param ctx + */ +static void free_secure_context(void *ctx) +{ + assert(ctx != NULL); + + ESP_LOGI(TAG, "Server shuts down, releasing SSL context"); + SSL_CTX_free(ctx); +} + +/** +* Create and perform basic init of a SSL_CTX, or return NULL on failure +* +* @return ctx or null +*/ +static SSL_CTX *create_secure_context(const struct httpd_ssl_config *config) +{ + SSL_CTX *ctx = NULL; + + ESP_LOGD(TAG, "SSL server context create"); + ctx = SSL_CTX_new(TLS_server_method()); + if (NULL != ctx) { + //region SSL ctx alloc'd + ESP_LOGD(TAG, "SSL ctx set own cert"); + if (SSL_CTX_use_certificate_ASN1(ctx, config->cacert_len, config->cacert_pem) + && SSL_CTX_use_PrivateKey_ASN1(0, ctx, config->prvtkey_pem, (long) config->prvtkey_len)) { + return ctx; + } + else { + ESP_LOGE(TAG, "Failed to set certificate"); + SSL_CTX_free(ctx); + ctx = NULL; + } + } else { + ESP_LOGE(TAG, "Failed to create SSL context"); + } + return NULL; +} + +/** Start the server */ +esp_err_t httpd_ssl_start(httpd_handle_t *pHandle, struct httpd_ssl_config *config) +{ + assert(config != NULL); + assert(pHandle != NULL); + + ESP_LOGI(TAG, "Starting server"); + + if (HTTPD_SSL_TRANSPORT_SECURE == config->transport_mode) { + SSL_CTX *ctx = create_secure_context(config); + if (!ctx) { + return ESP_FAIL; + } + + ESP_LOGD(TAG, "SSL context ready"); + + // set SSL specific config + config->httpd.global_transport_ctx = ctx; + config->httpd.global_transport_ctx_free_fn = free_secure_context; + config->httpd.open_fn = httpd_ssl_open; // the open function configures the created SSL sessions + + config->httpd.server_port = config->port_secure; + } else { + ESP_LOGD(TAG, "SSL disabled, using plain HTTP"); + config->httpd.server_port = config->port_insecure; + } + + httpd_handle_t handle = NULL; + + esp_err_t ret = httpd_start(&handle, &config->httpd); + if (ret != ESP_OK) return ret; + + *pHandle = handle; + + ESP_LOGI(TAG, "Server listening on port %d", config->httpd.server_port); + return ESP_OK; +} + +/** Stop the server */ +void httpd_ssl_stop(httpd_handle_t handle) +{ + httpd_stop(handle); +} diff --git a/docs/Doxyfile b/docs/Doxyfile index 3306fbc07..9e1bcdc9e 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -98,6 +98,7 @@ INPUT = \ ../../components/mdns/include/mdns.h \ ../../components/esp_http_client/include/esp_http_client.h \ ../../components/esp_http_server/include/esp_http_server.h \ + ../../components/esp_https_server/include/esp_https_server.h \ ## ## Provisioning - API Reference ## diff --git a/docs/en/api-reference/protocols/esp_https_server.rst b/docs/en/api-reference/protocols/esp_https_server.rst new file mode 100644 index 000000000..714afec3f --- /dev/null +++ b/docs/en/api-reference/protocols/esp_https_server.rst @@ -0,0 +1,51 @@ +HTTPS server +============ + +Overview +-------- + +This component is built on top of `esp_http_server`. The HTTPS server takes advantage of hooks and function overrides in the regular HTTP server to provide encryption using OpenSSL. + +All documentation for `esp_http_server` applies also to a server you create this way. + +Used APIs +--------- + +The following API of `esp_http_server` should not be used with `esp_https_server`, as they are used internally to handle secure sessions and to maintain internal state: + +- "send", "receive" and "pending" function overrides - secure socket handling + - :cpp:func:`httpd_set_sess_send_override` + - :cpp:func:`httpd_set_sess_recv_override` + - :cpp:func:`httpd_set_sess_pending_override` + - :cpp:func:`httpd_set_send_override` + - :cpp:func:`httpd_set_recv_override` + - :cpp:func:`httpd_set_pending_override` +- "transport context" - both global and session + - :cpp:func:`httpd_sess_get_transport_ctx` - returns SSL used for the session + - :cpp:func:`httpd_sess_set_transport_ctx` + - :cpp:func:`httpd_get_global_transport_ctx` - returns the shared SSL context + - :c:member:`httpd_config_t.global_transport_ctx` + - :c:member:`httpd_config_t.global_transport_ctx_free_fn` + - :c:member:`httpd_config_t.open_fn` - used to set up secure sockets + +Everything else can be used without limitations. + +Usage +----- + +Please see the example :example:`protocols/https_server` to learn how to set up a secure server. + +Basically all you need is to generate a certificate, embed it in the firmware, and provide its pointers and lengths to the start function via the init struct. + +The server can be started with or without SSL by changing a flag in the init struct. This could be used e.g. for testing or in trusted environments where you prefer speed over security. + +Performance +----------- + +The initial session setup can take about two seconds, or more with slower clock speeds or more verbose logging. Subsequent requests through the open secure socket are much faster (down to under +100 ms). + +API Reference +------------- + +.. include:: /_build/inc/esp_https_server.inc diff --git a/docs/en/api-reference/protocols/index.rst b/docs/en/api-reference/protocols/index.rst index fff67481f..f57b4cc4e 100644 --- a/docs/en/api-reference/protocols/index.rst +++ b/docs/en/api-reference/protocols/index.rst @@ -8,6 +8,7 @@ Protocols API ESP-TLS HTTP Client HTTP Server + HTTPS Server ASIO ESP-MQTT Modbus slave diff --git a/docs/zh_CN/api-reference/protocols/esp_https_server.rst b/docs/zh_CN/api-reference/protocols/esp_https_server.rst new file mode 100644 index 000000000..8f338a342 --- /dev/null +++ b/docs/zh_CN/api-reference/protocols/esp_https_server.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/protocols/esp_https_server.rst 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 } diff --git a/examples/protocols/https_server/CMakeLists.txt b/examples/protocols/https_server/CMakeLists.txt new file mode 100644 index 000000000..7f58e025f --- /dev/null +++ b/examples/protocols/https_server/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(https_server) diff --git a/examples/protocols/https_server/Makefile b/examples/protocols/https_server/Makefile new file mode 100644 index 000000000..5a20b6f57 --- /dev/null +++ b/examples/protocols/https_server/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := https_server + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/https_server/README.md b/examples/protocols/https_server/README.md new file mode 100644 index 000000000..08aa770c0 --- /dev/null +++ b/examples/protocols/https_server/README.md @@ -0,0 +1,24 @@ +# HTTP server with SSL support using OpenSSL + +This example creates a SSL server that returns a simple HTML page when you visit its root URL. + +See the `esp_https_server` component documentation for details. + +## Certificates + +You will need to approve a security exception in your browser. This is because of a self signed +certificate; this will be always the case, unless you preload the CA root into your browser/system +as trusted. + +You can generate a new certificate using the OpenSSL command line tool: + +``` +openssl req -newkey rsa:2048 -nodes -keyout prvtkey.pem -x509 -days 3650 -out cacert.pem -subj "/CN=ESP32 HTTPS server example" +``` + +Expiry time and metadata fields can be adjusted in the invocation. + +Please see the openssl man pages (man openssl-req) for more details. + +It is **strongly recommended** to not reuse the example certificate in your application; +it is included only for demonstration. diff --git a/examples/protocols/https_server/main/CMakeLists.txt b/examples/protocols/https_server/main/CMakeLists.txt new file mode 100644 index 000000000..9e08fb4d7 --- /dev/null +++ b/examples/protocols/https_server/main/CMakeLists.txt @@ -0,0 +1,8 @@ +set(COMPONENT_SRCS "main.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_EMBED_TXTFILES + "certs/cacert.pem" + "certs/prvtkey.pem") + +register_component() diff --git a/examples/protocols/https_server/main/Kconfig.projbuild b/examples/protocols/https_server/main/Kconfig.projbuild new file mode 100644 index 000000000..9e2813c69 --- /dev/null +++ b/examples/protocols/https_server/main/Kconfig.projbuild @@ -0,0 +1,16 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + Can be left blank if the network has no security set. + +endmenu diff --git a/examples/protocols/https_server/main/certs/cacert.pem b/examples/protocols/https_server/main/certs/cacert.pem new file mode 100644 index 000000000..cd2b80c82 --- /dev/null +++ b/examples/protocols/https_server/main/certs/cacert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL +BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx +MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ +UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T +sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k +qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd +GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4 +sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb +jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/ +ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3 +emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY +W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx +bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN +ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl +hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo= +-----END CERTIFICATE----- diff --git a/examples/protocols/https_server/main/certs/prvtkey.pem b/examples/protocols/https_server/main/certs/prvtkey.pem new file mode 100644 index 000000000..70d29078c --- /dev/null +++ b/examples/protocols/https_server/main/certs/prvtkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH +JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw +h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT +aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al +3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg +0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB +vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui +f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9 +Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y +JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX +49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc ++3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6 +pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D +0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG +YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV +MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL +CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin +7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1 +noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8 +4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g +Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/ +nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3 +q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2 +lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB +jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr +v/t+MeGJP/0Zw8v/X2CFll96 +-----END PRIVATE KEY----- diff --git a/examples/protocols/https_server/main/component.mk b/examples/protocols/https_server/main/component.mk new file mode 100644 index 000000000..b120d66e5 --- /dev/null +++ b/examples/protocols/https_server/main/component.mk @@ -0,0 +1,7 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_EMBED_TXTFILES := certs/cacert.pem +COMPONENT_EMBED_TXTFILES += certs/prvtkey.pem diff --git a/examples/protocols/https_server/main/main.c b/examples/protocols/https_server/main/main.c new file mode 100644 index 000000000..d46897f1b --- /dev/null +++ b/examples/protocols/https_server/main/main.c @@ -0,0 +1,150 @@ +/* Simple HTTP + SSL Server Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include + +#include + +/* A simple example that demonstrates how to create GET and POST + * handlers for the web server. + * The examples use simple WiFi configuration that you can set via + * 'make menuconfig'. + * If you'd rather not, just change the below entries to strings + * with the config you want - + * ie. #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +static const char *TAG="APP"; + + +/* An HTTP GET handler */ +esp_err_t root_get_handler(httpd_req_t *req) +{ + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, "

Hello Secure World!

", -1); // -1 = use strlen() + + return ESP_OK; +} + +const httpd_uri_t root = { + .uri = "/", + .method = HTTP_GET, + .handler = root_get_handler +}; + + +httpd_handle_t start_webserver(void) +{ + httpd_handle_t server = NULL; + + // Start the httpd server + ESP_LOGI(TAG, "Starting server"); + + httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT(); + + extern const unsigned char cacert_pem_start[] asm("_binary_cacert_pem_start"); + extern const unsigned char cacert_pem_end[] asm("_binary_cacert_pem_end"); + conf.cacert_pem = cacert_pem_start; + conf.cacert_len = cacert_pem_end - cacert_pem_start; + + extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start"); + extern const unsigned char prvtkey_pem_end[] asm("_binary_prvtkey_pem_end"); + conf.prvtkey_pem = prvtkey_pem_start; + conf.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start; + + esp_err_t ret = httpd_ssl_start(&server, &conf); + if (ESP_OK != ret) { + ESP_LOGI(TAG, "Error starting server!"); + return NULL; + } + + // Set URI handlers + ESP_LOGI(TAG, "Registering URI handlers"); + httpd_register_uri_handler(server, &root); + return server; +} + +void stop_webserver(httpd_handle_t server) +{ + // Stop the httpd server + httpd_ssl_stop(server); +} + + + + +// ------------------------- application boilerplate ------------------------ + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + httpd_handle_t *server = (httpd_handle_t *) ctx; + + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START"); + ESP_ERROR_CHECK(esp_wifi_connect()); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP"); + ESP_LOGI(TAG, "Got IP: '%s'", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + + /* Start the web server */ + if (*server == NULL) { + *server = start_webserver(); + } + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED"); + ESP_ERROR_CHECK(esp_wifi_connect()); + + /* Stop the web server */ + if (*server) { + stop_webserver(*server); + *server = NULL; + } + break; + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void *arg) +{ + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, arg)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); +} + +void app_main() +{ + static httpd_handle_t server = NULL; + ESP_ERROR_CHECK(nvs_flash_init()); + initialise_wifi(&server); +}