Merge branch 'feature/esp_https_server' into 'master'

Add HTTPS Server component

See merge request idf/esp-idf!3608
This commit is contained in:
Angus Gratton 2018-11-20 13:58:26 +08:00
commit 61ee1bd31f
25 changed files with 1191 additions and 103 deletions

View file

@ -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

View 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.
*
@ -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
* @}

View 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"));

View file

@ -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;

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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()

View file

@ -0,0 +1,2 @@
COMPONENT_SRCDIRS := src
COMPONENT_ADD_INCLUDEDIRS := include

View file

@ -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 <stdbool.h>
#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_

View file

@ -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 <string.h>
#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);
}

View file

@ -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
##

View file

@ -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

View file

@ -8,6 +8,7 @@ Protocols API
ESP-TLS <esp_tls>
HTTP Client <esp_http_client>
HTTP Server <esp_http_server>
HTTPS Server <esp_https_server>
ASIO <asio>
ESP-MQTT <mqtt>
Modbus slave <modbus>

View file

@ -0,0 +1 @@
.. include:: ../../../en/api-reference/protocols/esp_https_server.rst

View file

@ -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
}

View file

@ -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)

View file

@ -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

View file

@ -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.

View file

@ -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()

View file

@ -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

View file

@ -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-----

View file

@ -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-----

View file

@ -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

View file

@ -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 <esp_wifi.h>
#include <esp_event_loop.h>
#include <esp_log.h>
#include <esp_system.h>
#include <nvs_flash.h>
#include <sys/param.h>
#include <esp_https_server.h>
/* 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, "<h1>Hello Secure World!</h1>", -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);
}