chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,615 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _HTTPD_PRIV_H_
|
||||
#define _HTTPD_PRIV_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <netinet/in.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include "osal.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if CONFIG_LIBC_NEWLIB_NANO_FORMAT
|
||||
#define NEWLIB_NANO_COMPAT_FORMAT PRIu32
|
||||
#define NEWLIB_NANO_COMPAT_CAST(size_t_var) (uint32_t)size_t_var
|
||||
#else
|
||||
#define NEWLIB_NANO_COMPAT_FORMAT "zu"
|
||||
#define NEWLIB_NANO_COMPAT_CAST(size_t_var) size_t_var
|
||||
#endif
|
||||
|
||||
/* Size of request data block/chunk (not to be confused with chunked encoded data)
|
||||
* that is received and parsed in one turn of the parsing process. */
|
||||
#define PARSER_BLOCK_SIZE 128
|
||||
|
||||
/* Formats a log string to prepend context function name */
|
||||
#define LOG_FMT(x) "%s: " x, __func__
|
||||
|
||||
/**
|
||||
* @brief Control message data structure for internal use. Sent to control socket.
|
||||
*/
|
||||
struct httpd_ctrl_data {
|
||||
enum httpd_ctrl_msg {
|
||||
HTTPD_CTRL_SHUTDOWN,
|
||||
HTTPD_CTRL_WORK,
|
||||
HTTPD_CTRL_MAX,
|
||||
} hc_msg;
|
||||
httpd_work_fn_t hc_work;
|
||||
void *hc_work_arg;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Thread related data for internal use
|
||||
*/
|
||||
struct thread_data {
|
||||
othread_t handle; /*!< Handle to thread/task */
|
||||
enum {
|
||||
THREAD_IDLE = 0,
|
||||
THREAD_RUNNING,
|
||||
THREAD_STOPPING,
|
||||
THREAD_STOPPED,
|
||||
} status; /*!< State of the thread */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A database of all the open sockets in the system.
|
||||
*/
|
||||
struct sock_db {
|
||||
int fd; /*!< The file descriptor for this socket */
|
||||
void *ctx; /*!< A custom context for this socket */
|
||||
bool ignore_sess_ctx_changes; /*!< Flag indicating if session context changes should be ignored */
|
||||
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_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; /*!< Receive function for this socket */
|
||||
httpd_pending_func_t pending_fn; /*!< Pending function for this socket */
|
||||
uint64_t lru_counter; /*!< LRU Counter indicating when the socket was last used */
|
||||
bool lru_socket; /*!< Flag indicating LRU socket */
|
||||
char pending_data[PARSER_BLOCK_SIZE]; /*!< Buffer for pending data to be received */
|
||||
size_t pending_len; /*!< Length of pending data to be received */
|
||||
bool for_async_req; /*!< If true, the socket will not be LRU purged */
|
||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||
bool ws_handshake_done; /*!< True if it has done WebSocket handshake (if this socket is a valid WS) */
|
||||
bool ws_close; /*!< Set to true to close the socket later (when WS Close frame received) */
|
||||
esp_err_t (*ws_handler)(httpd_req_t *r); /*!< WebSocket handler, leave to null if it's not WebSocket */
|
||||
bool ws_control_frames; /*!< WebSocket flag indicating that control frames should be passed to user handlers */
|
||||
void *ws_user_ctx; /*!< Pointer to user context data which will be available to handler for websocket*/
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Auxiliary data structure for use during reception and processing
|
||||
* of requests and temporarily keeping responses
|
||||
*/
|
||||
struct httpd_req_aux {
|
||||
struct sock_db *sd; /*!< Pointer to socket database */
|
||||
char *scratch; /*!< Temporary buffer for our operations (1 byte extra for null termination) */
|
||||
size_t scratch_size_limit; /*!< Scratch buffer size limit (By default this value is set to CONFIG_HTTPD_MAX_REQ_HDR_LEN, overwrite is possible) */
|
||||
size_t scratch_cur_size; /*!< Scratch buffer cur size (By default this value is set to CONFIG_HTTPD_MAX_URI_LEN, overwrite is possible) */
|
||||
size_t max_req_hdr_len; /*!< Header buffer size limit */
|
||||
size_t max_uri_len; /*!< URI buffer size limit */
|
||||
size_t remaining_len; /*!< Amount of data remaining to be fetched */
|
||||
char *status; /*!< HTTP response's status code */
|
||||
char *content_type; /*!< HTTP response's content type */
|
||||
bool first_chunk_sent; /*!< Used to indicate if first chunk sent */
|
||||
unsigned req_hdrs_count; /*!< Count of total headers in request packet */
|
||||
unsigned resp_hdrs_count; /*!< Count of additional headers in response packet */
|
||||
struct resp_hdr {
|
||||
const char *field;
|
||||
const char *value;
|
||||
} *resp_hdrs; /*!< Additional headers in response packet */
|
||||
struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */
|
||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||
bool ws_handshake_detect; /*!< WebSocket handshake detection flag */
|
||||
httpd_ws_type_t ws_type; /*!< WebSocket frame type */
|
||||
bool ws_final; /*!< WebSocket FIN bit (final frame or not) */
|
||||
uint8_t mask_key[4]; /*!< WebSocket mask key for this payload */
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Server data for each instance. This is exposed publicly as
|
||||
* httpd_handle_t but internal structure/members are kept private.
|
||||
*/
|
||||
struct httpd_data {
|
||||
httpd_config_t config; /*!< HTTPD server configuration */
|
||||
int listen_fd; /*!< Server listener FD */
|
||||
int ctrl_fd; /*!< Ctrl message receiver FD */
|
||||
SemaphoreHandle_t ctrl_sock_semaphore; /*!< Ctrl mbox slot reservation (sized to LWIP_UDP_RECVMBOX_SIZE) */
|
||||
int msg_fd; /*!< Ctrl message sender FD */
|
||||
struct thread_data hd_td; /*!< Information for the HTTPD thread */
|
||||
struct sock_db *hd_sd; /*!< The socket database */
|
||||
int hd_sd_active_count; /*!< The number of the active sockets */
|
||||
httpd_uri_t **hd_calls; /*!< Registered URI handlers */
|
||||
struct httpd_req hd_req; /*!< The current HTTPD request */
|
||||
struct httpd_req_aux hd_req_aux; /*!< Additional data about the HTTPD request kept unexposed */
|
||||
uint64_t lru_counter; /*!< LRU counter */
|
||||
esp_http_server_event_id_t http_server_state; /*!< HTTPD server state */
|
||||
|
||||
/* Array of registered error handler functions */
|
||||
httpd_err_handler_func_t *err_handler_fns;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Options for receiving HTTP request data
|
||||
*/
|
||||
typedef enum {
|
||||
HTTPD_RECV_OPT_NONE = 0,
|
||||
HTTPD_RECV_OPT_HALT_AFTER_PENDING = 1, /*!< Halt immediately after receiving from pending buffer */
|
||||
HTTPD_RECV_OPT_BLOCKING = 2, /*!< Receive blocking (don't return partial length) */
|
||||
} httpd_recv_opt_t;
|
||||
|
||||
/******************* Group : Session Management ********************/
|
||||
/** @name Session Management
|
||||
* Functions related to HTTP session management
|
||||
* @{
|
||||
*/
|
||||
|
||||
// Enum function, which will be called for each session
|
||||
typedef int (*httpd_session_enum_function)(struct sock_db *session, void *context);
|
||||
|
||||
/**
|
||||
* @brief Enumerates all sessions
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
* @param[in] enum_function Enumeration function, which will be called for each session
|
||||
* @param[in] context Context, which will be passed to the enumeration function
|
||||
*/
|
||||
void httpd_sess_enum(struct httpd_data *hd, httpd_session_enum_function enum_function, void *context);
|
||||
|
||||
/**
|
||||
* @brief Returns next free session slot (fd<0)
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
*
|
||||
* @return
|
||||
* - +VE : Free session slot
|
||||
* - NULL: End of iteration
|
||||
*/
|
||||
struct sock_db *httpd_sess_get_free(struct httpd_data *hd);
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
*/
|
||||
void httpd_sess_init(struct httpd_data *hd);
|
||||
|
||||
/**
|
||||
* @brief Starts a new session for client requesting connection and adds
|
||||
* it's descriptor to the socket database.
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
* @param[in] newfd Descriptor of the new client to be added to the session.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : on successfully queuing the work
|
||||
* - ESP_FAIL : in case of control socket error while sending
|
||||
*/
|
||||
esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd);
|
||||
|
||||
/**
|
||||
* @brief Processes incoming HTTP requests
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
* @param[in] session Session
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : on successfully receiving, parsing and responding to a request
|
||||
* - ESP_FAIL : in case of failure in any of the stages of processing
|
||||
*/
|
||||
esp_err_t httpd_sess_process(struct httpd_data *hd, struct sock_db *session);
|
||||
|
||||
/**
|
||||
* @brief Remove client descriptor from the session / socket database
|
||||
* and close the connection for this client.
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
* @param[in] session Session
|
||||
*/
|
||||
void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session);
|
||||
|
||||
/**
|
||||
* @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 fdset and
|
||||
* update the value of maxfd which are needed by the select function
|
||||
* for looking through all available sockets for incoming data.
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
* @param[out] fdset File descriptor set to be updated.
|
||||
* @param[out] maxfd Maximum value among all file descriptors.
|
||||
*/
|
||||
void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd);
|
||||
|
||||
/**
|
||||
* @brief Checks if session can accept another connection from new client.
|
||||
* If sockets database is full then this returns false.
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
*
|
||||
* @return True if session can accept new clients
|
||||
*/
|
||||
bool httpd_is_sess_available(struct httpd_data *hd);
|
||||
|
||||
/**
|
||||
* @brief Checks if session has any pending data/packets
|
||||
* for processing
|
||||
*
|
||||
* This is needed as httpd_unrecv may un-receive next
|
||||
* packet in the stream. If only partial packet was
|
||||
* received then select() would mark the fd for processing
|
||||
* as remaining part of the packet would still be in socket
|
||||
* recv queue. But if a complete packet got unreceived
|
||||
* then it would not be processed until further data is
|
||||
* received on the socket. This is when this function
|
||||
* comes in use, as it checks the socket's pending data
|
||||
* buffer.
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
* @param[in] session Session
|
||||
*
|
||||
* @return True if there is any pending data
|
||||
*/
|
||||
bool httpd_sess_pending(struct httpd_data *hd, struct sock_db *session);
|
||||
|
||||
/**
|
||||
* @brief Removes the least recently used client from the session
|
||||
*
|
||||
* This may be useful if new clients are requesting for connection but
|
||||
* max number of connections is reached, in which case the client which
|
||||
* is inactive for the longest will be removed from the session.
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : if session closure initiated successfully
|
||||
* - ESP_FAIL : if failed
|
||||
*/
|
||||
esp_err_t httpd_sess_close_lru(struct httpd_data *hd);
|
||||
|
||||
/**
|
||||
* @brief Closes all sessions
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
*
|
||||
*/
|
||||
void httpd_sess_close_all(struct httpd_data *hd);
|
||||
|
||||
/** End of Group : Session Management
|
||||
* @}
|
||||
*/
|
||||
|
||||
/****************** Group : URI Handling ********************/
|
||||
/** @name URI Handling
|
||||
* Methods for accessing URI handlers
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief For an HTTP request, searches through all the registered URI handlers
|
||||
* and invokes the appropriate one if found
|
||||
*
|
||||
* @param[in] hd Server instance data for which handler needs to be invoked
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : if handler found and executed successfully
|
||||
* - ESP_FAIL : otherwise
|
||||
*/
|
||||
esp_err_t httpd_uri(struct httpd_data *hd);
|
||||
|
||||
/**
|
||||
* @brief Unregister all URI handlers
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
*/
|
||||
void httpd_unregister_all_uri_handlers(struct httpd_data *hd);
|
||||
|
||||
/**
|
||||
* @brief Validates the request to prevent users from calling APIs, that are to
|
||||
* be called only inside a URI handler, outside the handler context
|
||||
*
|
||||
* @param[in] req Pointer to HTTP request that needs to be validated
|
||||
*
|
||||
* @return
|
||||
* - true : if valid request
|
||||
* - false : otherwise
|
||||
*/
|
||||
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 default 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
|
||||
* @}
|
||||
*/
|
||||
|
||||
/****************** Group : Processing ********************/
|
||||
/** @name Processing
|
||||
* Methods for processing HTTP requests
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Initiates the processing of HTTP request
|
||||
*
|
||||
* Receives incoming TCP packet on a socket, then parses the packet as
|
||||
* HTTP request and fills httpd_req_t data structure with the extracted
|
||||
* URI, headers are ready to be fetched from scratch buffer and calling
|
||||
* http_recv() after this reads the body of the request.
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
* @param[in] sd Pointer to socket which is needed for receiving TCP packets.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : if request packet is valid
|
||||
* - ESP_FAIL : otherwise
|
||||
*/
|
||||
esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd);
|
||||
|
||||
/**
|
||||
* @brief For an HTTP request, resets the resources allocated for it and
|
||||
* purges any data left to be received
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : if request packet deleted and resources cleaned.
|
||||
* - ESP_FAIL : otherwise.
|
||||
*/
|
||||
esp_err_t httpd_req_delete(struct httpd_data *hd);
|
||||
|
||||
/** End of Group : Parsing
|
||||
* @}
|
||||
*/
|
||||
|
||||
/****************** Group : Send/Receive ********************/
|
||||
/** @name Send and Receive
|
||||
* Methods for transmitting and receiving HTTP requests and responses
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief For sending out data in response to an HTTP request.
|
||||
*
|
||||
* @param[in] req Pointer to the HTTP request for which the response needs to be sent
|
||||
* @param[in] buf Pointer to the buffer from where the body of the response is taken
|
||||
* @param[in] buf_len Length of the buffer
|
||||
*
|
||||
* @return
|
||||
* - Length of data : if successful
|
||||
* - ESP_FAIL : if failed
|
||||
*/
|
||||
int httpd_send(httpd_req_t *req, const char *buf, size_t buf_len);
|
||||
|
||||
/**
|
||||
* @brief For receiving HTTP request data
|
||||
*
|
||||
* @note The exposed API httpd_recv() is simply this function with last parameter
|
||||
* set as false. This function is used internally during reception and
|
||||
* processing of a new request.
|
||||
*
|
||||
* There are 2 options available that affect the behavior of the function:
|
||||
* - HTTPD_RECV_OPT_HALT_AFTER_PENDING
|
||||
* The option to halt after receiving pending data prevents the server from
|
||||
* requesting more data than is needed for completing a packet in case when
|
||||
* all the remaining part of the packet is in the pending buffer.
|
||||
*
|
||||
* - HTTPD_RECV_OPT_BLOCKING
|
||||
* The option to not return until the `buf_len` bytes have been read.
|
||||
*
|
||||
* @param[in] req Pointer to new HTTP request which only has the socket descriptor
|
||||
* @param[out] buf Pointer to the buffer which will be filled with the received data
|
||||
* @param[in] buf_len Length of the buffer
|
||||
* @param[in] opt Receive option
|
||||
*
|
||||
* @return
|
||||
* - Length of data : if successful
|
||||
* - ESP_FAIL : if failed
|
||||
*/
|
||||
int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t buf_len, httpd_recv_opt_t opt);
|
||||
|
||||
/**
|
||||
* @brief For un-receiving HTTP request data
|
||||
*
|
||||
* This function copies data into internal buffer pending_data so that
|
||||
* when httpd_recv is called, it first fetches this pending data and
|
||||
* then only starts receiving from the socket
|
||||
*
|
||||
* @note If data is too large for the internal buffer then only
|
||||
* part of the data is unreceived, reflected in the returned
|
||||
* length. Make sure that such truncation is checked for and
|
||||
* handled properly.
|
||||
*
|
||||
* @param[in] req Pointer to new HTTP request which only has the socket descriptor
|
||||
* @param[in] buf Pointer to the buffer from where data needs to be un-received
|
||||
* @param[in] buf_len Length of the buffer
|
||||
*
|
||||
* @return Length of data copied into pending buffer
|
||||
*/
|
||||
size_t httpd_unrecv(struct httpd_req *r, const char *buf, size_t buf_len);
|
||||
|
||||
/**
|
||||
* @brief This is the low level default send function of the HTTPD. This should
|
||||
* 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
|
||||
* @param[in] flags Flags for mode selection
|
||||
*
|
||||
* @return
|
||||
* - Length of data : if successful
|
||||
* - -1 : if failed (appropriate errno is set)
|
||||
*/
|
||||
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
|
||||
* @param[in] flags Flags for mode selection
|
||||
*
|
||||
* @return
|
||||
* - Length of data : if successful
|
||||
* - -1 : if failed (appropriate errno is set)
|
||||
*/
|
||||
int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags);
|
||||
|
||||
/** End of Group : Send and Receive
|
||||
* @}
|
||||
*/
|
||||
|
||||
/* ************** Group: WebSocket ************** */
|
||||
/** @name WebSocket
|
||||
* Functions for WebSocket header parsing
|
||||
* @{
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Respond to a WebSocket opening handshake.
|
||||
*
|
||||
* On any RFC 6455 §4.2.1 handshake validation failure (missing Host,
|
||||
* malformed Sec-WebSocket-Key, unsupported Sec-WebSocket-Version, etc.),
|
||||
* this function sends the appropriate HTTP error response (400 Bad Request
|
||||
* or 426 Upgrade Required) to the client itself and returns ESP_FAIL.
|
||||
* Callers MUST NOT attempt to send another response on the ESP_FAIL path.
|
||||
*
|
||||
* @param[in] req Pointer to handshake request that will be handled
|
||||
* @param[in] supported_subprotocol Pointer to the subprotocol supported by this URI
|
||||
* @return
|
||||
* - ESP_OK : Handshake successful; 101 Switching Protocols sent
|
||||
* - ESP_ERR_INVALID_ARG : @p req or its aux pointer is NULL
|
||||
* - ESP_ERR_INVALID_STATE : Handshake was already performed on this session
|
||||
* - ESP_FAIL : Handshake-validation failure (HTTP error already sent),
|
||||
* memory allocation failure, hash/encode failure,
|
||||
* or socket send failure
|
||||
*/
|
||||
esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req, const char *supported_subprotocol);
|
||||
|
||||
/**
|
||||
* @brief This function is for getting a frame type
|
||||
* and responding a WebSocket control frame automatically
|
||||
*
|
||||
* @param[in] req Pointer to handshake request that will be handled
|
||||
* @return
|
||||
* - ESP_OK : When handshake is successful
|
||||
* - ESP_ERR_INVALID_ARG : Argument is invalid (null or non-WebSocket)
|
||||
* - ESP_ERR_INVALID_STATE : Received only some parts of a control frame
|
||||
* - ESP_FAIL : Socket failures
|
||||
*/
|
||||
esp_err_t httpd_ws_get_frame_type(httpd_req_t *req);
|
||||
|
||||
/**
|
||||
* @brief Trigger an httpd session close externally
|
||||
*
|
||||
* @note Calling this API is only required in special circumstances wherein
|
||||
* some application requires to close an httpd client session asynchronously.
|
||||
*
|
||||
* @param[in] handle Handle to server returned by httpd_start
|
||||
* @param[in] session Session to be closed
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : On successfully initiating closure
|
||||
* - ESP_FAIL : Failure to queue work
|
||||
* - ESP_ERR_NOT_FOUND : Socket fd not found
|
||||
* - ESP_ERR_INVALID_ARG : Null arguments
|
||||
*/
|
||||
esp_err_t httpd_sess_trigger_close_(httpd_handle_t handle, struct sock_db *session);
|
||||
|
||||
/**
|
||||
* @brief Directly closes the least recently used session
|
||||
*
|
||||
* @param[in] hd Server instance data
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : if session closed successfully
|
||||
*/
|
||||
esp_err_t httpd_sess_close_lru_direct(struct httpd_data *hd);
|
||||
|
||||
/** End of WebSocket related functions
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_HTTPD_ENABLE_EVENTS
|
||||
|
||||
#if CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT == -1
|
||||
#define ESP_HTTP_SERVER_EVENT_POST_TIMEOUT portMAX_DELAY
|
||||
#else
|
||||
#define ESP_HTTP_SERVER_EVENT_POST_TIMEOUT pdMS_TO_TICKS(CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Function to dispatch events in default event loop
|
||||
*
|
||||
*/
|
||||
void esp_http_server_dispatch_event(int32_t event_id, const void* event_data, size_t event_data_size);
|
||||
|
||||
#else // CONFIG_HTTPD_ENABLE_EVENTS
|
||||
static inline void esp_http_server_dispatch_event(int32_t event_id, const void* event_data, size_t event_data_size)
|
||||
{
|
||||
// Events disabled, do nothing
|
||||
(void) event_id;
|
||||
(void) event_data;
|
||||
(void) event_data_size;
|
||||
}
|
||||
#endif // CONFIG_HTTPD_ENABLE_EVENTS
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ! _HTTPD_PRIV_H_ */
|
||||
@@ -0,0 +1,645 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <errno.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <assert.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include "esp_httpd_priv.h"
|
||||
#include "ctrl_sock.h"
|
||||
#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING
|
||||
#include "freertos/semphr.h"
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_LWIP_MAX_SOCKETS)
|
||||
#define HTTPD_MAX_SOCKETS CONFIG_LWIP_MAX_SOCKETS
|
||||
#else
|
||||
/* LwIP component is not included into the build, use a default value */
|
||||
#define HTTPD_MAX_SOCKETS 15
|
||||
#endif
|
||||
|
||||
static const int DEFAULT_KEEP_ALIVE_IDLE = 5;
|
||||
static const int DEFAULT_KEEP_ALIVE_INTERVAL= 5;
|
||||
static const int DEFAULT_KEEP_ALIVE_COUNT= 3;
|
||||
|
||||
typedef struct {
|
||||
fd_set *fdset;
|
||||
struct httpd_data *hd;
|
||||
} process_session_context_t;
|
||||
|
||||
static const char *TAG = "httpd";
|
||||
|
||||
#ifdef CONFIG_HTTPD_ENABLE_EVENTS
|
||||
ESP_EVENT_DEFINE_BASE(ESP_HTTP_SERVER_EVENT);
|
||||
|
||||
void esp_http_server_dispatch_event(int32_t event_id, const void* event_data, size_t event_data_size)
|
||||
{
|
||||
esp_err_t err = esp_event_post(ESP_HTTP_SERVER_EVENT, event_id, event_data, event_data_size, ESP_HTTP_SERVER_EVENT_POST_TIMEOUT);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to post esp_http_server event: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_HTTPD_ENABLE_EVENTS
|
||||
|
||||
static esp_err_t httpd_accept_conn(struct httpd_data *hd)
|
||||
{
|
||||
/* If no space is available for new session, close the least recently used one */
|
||||
if (hd->config.lru_purge_enable == true) {
|
||||
if (!httpd_is_sess_available(hd)) {
|
||||
/* Queue asynchronous closure of the least recently used session */
|
||||
#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING
|
||||
/* In case of blocking mode, close the least recently used session directly */
|
||||
return httpd_sess_close_lru_direct(hd);
|
||||
#else
|
||||
return httpd_sess_close_lru(hd);
|
||||
#endif /* CONFIG_HTTPD_QUEUE_WORK_BLOCKING */
|
||||
/* Returning from this allows the main server thread to process
|
||||
* the queued asynchronous control message for closing LRU session.
|
||||
* Since connection request hasn't been addressed yet using accept()
|
||||
* therefore httpd_accept_conn() will be called again, but this time
|
||||
* with space available for one session
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
struct sockaddr_storage addr_from;
|
||||
socklen_t addr_from_len = sizeof(addr_from);
|
||||
|
||||
int new_fd = accept(hd->listen_fd, (struct sockaddr *)&addr_from, &addr_from_len);
|
||||
if (new_fd < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in accept (%d)"), errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd);
|
||||
|
||||
struct timeval tv;
|
||||
/* Set recv timeout of this fd as per config */
|
||||
tv.tv_sec = hd->config.recv_wait_timeout;
|
||||
tv.tv_usec = 0;
|
||||
if (setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv)) < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in setsockopt SO_RCVTIMEO (%d)"), errno);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Set send timeout of this fd as per config */
|
||||
tv.tv_sec = hd->config.send_wait_timeout;
|
||||
tv.tv_usec = 0;
|
||||
if (setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv)) < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in setsockopt SO_SNDTIMEO (%d)"), errno);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (hd->config.keep_alive_enable) {
|
||||
int keep_alive_enable = 1;
|
||||
int keep_alive_idle = hd->config.keep_alive_idle ? hd->config.keep_alive_idle : DEFAULT_KEEP_ALIVE_IDLE;
|
||||
int keep_alive_interval = hd->config.keep_alive_interval ? hd->config.keep_alive_interval : DEFAULT_KEEP_ALIVE_INTERVAL;
|
||||
int keep_alive_count = hd->config.keep_alive_count ? hd->config.keep_alive_count : DEFAULT_KEEP_ALIVE_COUNT;
|
||||
ESP_LOGD(TAG, "Enable TCP keep alive. idle: %d, interval: %d, count: %d", keep_alive_idle, keep_alive_interval, keep_alive_count);
|
||||
|
||||
if (setsockopt(new_fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive_enable, sizeof(keep_alive_enable)) < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in setsockopt SO_KEEPALIVE (%d)"), errno);
|
||||
goto exit;
|
||||
}
|
||||
#ifndef __APPLE__
|
||||
if (setsockopt(new_fd, IPPROTO_TCP, TCP_KEEPIDLE, &keep_alive_idle, sizeof(keep_alive_idle)) < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in setsockopt TCP_KEEPIDLE (%d)"), errno);
|
||||
goto exit;
|
||||
}
|
||||
if (setsockopt(new_fd, IPPROTO_TCP, TCP_KEEPINTVL, &keep_alive_interval, sizeof(keep_alive_interval)) < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in setsockopt TCP_KEEPINTVL (%d)"), errno);
|
||||
goto exit;
|
||||
}
|
||||
if (setsockopt(new_fd, IPPROTO_TCP, TCP_KEEPCNT, &keep_alive_count, sizeof(keep_alive_count)) < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in setsockopt TCP_KEEPCNT (%d)"), errno);
|
||||
goto exit;
|
||||
}
|
||||
#else // __APPLE__
|
||||
if (setsockopt(new_fd, IPPROTO_TCP, TCP_KEEPALIVE, &keep_alive_idle, sizeof(keep_alive_idle)) < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in setsockopt TCP_KEEPALIVE (%d)"), errno);
|
||||
goto exit;
|
||||
}
|
||||
#endif // __APPLE__
|
||||
}
|
||||
if (ESP_OK != httpd_sess_new(hd, new_fd)) {
|
||||
ESP_LOGE(TAG, LOG_FMT("session creation failed"));
|
||||
goto exit;
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("complete"));
|
||||
hd->http_server_state = HTTP_SERVER_EVENT_ON_CONNECTED;
|
||||
esp_http_server_dispatch_event(HTTP_SERVER_EVENT_ON_CONNECTED, &new_fd, sizeof(int));
|
||||
return ESP_OK;
|
||||
exit:
|
||||
close(new_fd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *arg)
|
||||
{
|
||||
if (handle == NULL || work == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
struct httpd_ctrl_data msg = {
|
||||
.hc_msg = HTTPD_CTRL_WORK,
|
||||
.hc_work = work,
|
||||
.hc_work_arg = arg,
|
||||
};
|
||||
|
||||
/* Reserve a slot in the control mbox before sending. In blocking mode
|
||||
* the caller waits for a slot; in the default non-blocking mode we
|
||||
* fail fast so the caller knows the work was not queued. */
|
||||
#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING
|
||||
const TickType_t wait = portMAX_DELAY;
|
||||
#else
|
||||
const TickType_t wait = 0;
|
||||
#endif
|
||||
if (xSemaphoreTake(hd->ctrl_sock_semaphore, wait) != pdTRUE) {
|
||||
ESP_LOGW(TAG, LOG_FMT("ctrl socket queue full, work not queued"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
int ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
|
||||
if (ret < 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("failed to queue work"));
|
||||
xSemaphoreGive(hd->ctrl_sock_semaphore);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t httpd_get_client_list(httpd_handle_t handle, size_t *fds, int *client_fds)
|
||||
{
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
if (hd == NULL || fds == NULL || *fds == 0 || client_fds == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
size_t max_fds = *fds;
|
||||
*fds = 0;
|
||||
for (int i = 0; i < hd->config.max_open_sockets; ++i) {
|
||||
if (hd->hd_sd[i].fd != -1) {
|
||||
if (*fds < max_fds) {
|
||||
client_fds[(*fds)++] = hd->hd_sd[i].fd;
|
||||
} else {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void *httpd_get_global_user_ctx(httpd_handle_t handle)
|
||||
{
|
||||
return ((struct httpd_data *)handle)->config.global_user_ctx;
|
||||
}
|
||||
|
||||
void *httpd_get_global_transport_ctx(httpd_handle_t handle)
|
||||
{
|
||||
return ((struct httpd_data *)handle)->config.global_transport_ctx;
|
||||
}
|
||||
|
||||
|
||||
static void httpd_process_ctrl_msg(struct httpd_data *hd)
|
||||
{
|
||||
struct httpd_ctrl_data msg;
|
||||
int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);
|
||||
if (ret <= 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);
|
||||
/* No packet was actually consumed from the mbox here, so this give
|
||||
* is unbalanced. It's tolerated because the counting semaphore is
|
||||
* capped at its max — excess gives become no-ops. Spurious recv
|
||||
* errors after select() are rare in practice. */
|
||||
xSemaphoreGive(hd->ctrl_sock_semaphore);
|
||||
return;
|
||||
}
|
||||
if (ret != sizeof(msg)) {
|
||||
ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
|
||||
xSemaphoreGive(hd->ctrl_sock_semaphore);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.hc_msg) {
|
||||
case HTTPD_CTRL_WORK:
|
||||
if (msg.hc_work) {
|
||||
ESP_LOGD(TAG, LOG_FMT("work"));
|
||||
(*msg.hc_work)(msg.hc_work_arg);
|
||||
}
|
||||
break;
|
||||
case HTTPD_CTRL_SHUTDOWN:
|
||||
ESP_LOGD(TAG, LOG_FMT("shutdown"));
|
||||
hd->hd_td.status = THREAD_STOPPING;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
xSemaphoreGive(hd->ctrl_sock_semaphore);
|
||||
}
|
||||
|
||||
// Called for each session from httpd_server
|
||||
static int httpd_process_session(struct sock_db *session, void *context)
|
||||
{
|
||||
if ((!session) || (!context)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (session->fd < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// session is busy in an async task, do not process here.
|
||||
if (session->for_async_req) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
process_session_context_t *ctx = (process_session_context_t *)context;
|
||||
int fd = session->fd;
|
||||
|
||||
if (FD_ISSET(fd, ctx->fdset) || httpd_sess_pending(ctx->hd, session)) {
|
||||
ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
|
||||
if (httpd_sess_process(ctx->hd, session) != ESP_OK) {
|
||||
httpd_sess_delete(ctx->hd, session); // Delete session
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Manage in-coming connection or data requests */
|
||||
static esp_err_t httpd_server(struct httpd_data *hd)
|
||||
{
|
||||
fd_set read_set;
|
||||
FD_ZERO(&read_set);
|
||||
if (hd->config.lru_purge_enable || httpd_is_sess_available(hd)) {
|
||||
/* Only listen for new connections if server has capacity to
|
||||
* handle more (or when LRU purge is enabled, in which case
|
||||
* older connections will be closed) */
|
||||
FD_SET(hd->listen_fd, &read_set);
|
||||
}
|
||||
FD_SET(hd->ctrl_fd, &read_set);
|
||||
|
||||
int tmp_max_fd;
|
||||
httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);
|
||||
int maxfd = MAX(hd->listen_fd, tmp_max_fd);
|
||||
tmp_max_fd = maxfd;
|
||||
maxfd = MAX(hd->ctrl_fd, tmp_max_fd);
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("doing select maxfd+1 = %d"), maxfd + 1);
|
||||
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);
|
||||
httpd_sess_delete_invalid(hd);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Case0: Do we have a control message? */
|
||||
if (FD_ISSET(hd->ctrl_fd, &read_set)) {
|
||||
ESP_LOGD(TAG, LOG_FMT("processing ctrl message"));
|
||||
httpd_process_ctrl_msg(hd);
|
||||
if (hd->hd_td.status == THREAD_STOPPING) {
|
||||
ESP_LOGD(TAG, LOG_FMT("stopping thread"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Case1: Do we have any activity on the current data
|
||||
* sessions? */
|
||||
process_session_context_t context = {
|
||||
.fdset = &read_set,
|
||||
.hd = hd
|
||||
};
|
||||
httpd_sess_enum(hd, httpd_process_session, &context);
|
||||
|
||||
/* Case2: Do we have any incoming connection requests to
|
||||
* process? */
|
||||
if (FD_ISSET(hd->listen_fd, &read_set)) {
|
||||
ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd);
|
||||
if (httpd_accept_conn(hd) != ESP_OK) {
|
||||
ESP_LOGW(TAG, LOG_FMT("error accepting new connection"));
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* The main HTTPD thread */
|
||||
static void httpd_thread(void *arg)
|
||||
{
|
||||
int ret;
|
||||
struct httpd_data *hd = (struct httpd_data *) arg;
|
||||
hd->hd_td.status = THREAD_RUNNING;
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("web server started"));
|
||||
while (1) {
|
||||
ret = httpd_server(hd);
|
||||
if (ret != ESP_OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("web server exiting"));
|
||||
close(hd->msg_fd);
|
||||
cs_free_ctrl_sock(hd->ctrl_fd);
|
||||
httpd_sess_close_all(hd);
|
||||
close(hd->listen_fd);
|
||||
hd->hd_td.status = THREAD_STOPPED;
|
||||
httpd_os_thread_delete();
|
||||
}
|
||||
|
||||
static esp_err_t httpd_server_init(struct httpd_data *hd)
|
||||
{
|
||||
#if CONFIG_LWIP_IPV6
|
||||
int fd = socket(PF_INET6, SOCK_STREAM, 0);
|
||||
#else
|
||||
int fd = socket(PF_INET, SOCK_STREAM, 0);
|
||||
#endif
|
||||
if (fd < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
#if CONFIG_LWIP_IPV6
|
||||
struct in6_addr inaddr_any = IN6ADDR_ANY_INIT;
|
||||
struct sockaddr_in6 serv_addr = {
|
||||
.sin6_family = PF_INET6,
|
||||
.sin6_addr = inaddr_any,
|
||||
.sin6_port = htons(hd->config.server_port)
|
||||
};
|
||||
#else
|
||||
struct sockaddr_in serv_addr = {
|
||||
.sin_family = PF_INET,
|
||||
.sin_addr = {
|
||||
.s_addr = htonl(INADDR_ANY)
|
||||
},
|
||||
.sin_port = htons(hd->config.server_port)
|
||||
};
|
||||
#endif
|
||||
/* Enable SO_REUSEADDR to allow binding to the same
|
||||
* address and port when restarting the server */
|
||||
int enable = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
|
||||
/* This will fail if CONFIG_LWIP_SO_REUSE is not enabled. But
|
||||
* it does not affect the normal working of the HTTP Server */
|
||||
ESP_LOGW(TAG, LOG_FMT("error in setsockopt SO_REUSEADDR (%d)"), errno);
|
||||
}
|
||||
|
||||
/* Bind to specific network interface if configured */
|
||||
if (hd->config.if_name && hd->config.if_name->ifr_name[0] != 0) {
|
||||
ESP_LOGI(TAG, LOG_FMT("Binding server to interface: %s"), hd->config.if_name->ifr_name);
|
||||
#ifndef __APPLE__
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, hd->config.if_name, sizeof(struct ifreq)) < 0) {
|
||||
#else
|
||||
int idx = if_nametoindex(hd->config.if_name->ifr_name);
|
||||
if (idx == 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("Invalid interface name %s"), hd->config.if_name->ifr_name);
|
||||
close(fd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx)) < 0) {
|
||||
#endif
|
||||
ESP_LOGE(TAG, LOG_FMT("Failed to bind to interface %s (%d)"), hd->config.if_name->ifr_name, errno);
|
||||
close(fd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
|
||||
close(fd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ret = listen(fd, hd->config.backlog_conn);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);
|
||||
close(fd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);
|
||||
if (ctrl_fd < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);
|
||||
close(fd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (msg_fd < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);
|
||||
close(fd);
|
||||
close(ctrl_fd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
hd->listen_fd = fd;
|
||||
hd->ctrl_fd = ctrl_fd;
|
||||
hd->msg_fd = msg_fd;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static struct httpd_data *httpd_create(const httpd_config_t *config)
|
||||
{
|
||||
/* Allocate memory for httpd instance data */
|
||||
struct httpd_data *hd = calloc(1, sizeof(struct httpd_data));
|
||||
if (!hd) {
|
||||
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP server instance"));
|
||||
return NULL;
|
||||
}
|
||||
hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
|
||||
if (!hd->hd_calls) {
|
||||
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP URI handlers"));
|
||||
free(hd);
|
||||
return NULL;
|
||||
}
|
||||
hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
|
||||
if (!hd->hd_sd) {
|
||||
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP session data"));
|
||||
free(hd->hd_calls);
|
||||
free(hd);
|
||||
return NULL;
|
||||
}
|
||||
struct httpd_req_aux *ra = &hd->hd_req_aux;
|
||||
ra->resp_hdrs = calloc(config->max_resp_headers, sizeof(struct resp_hdr));
|
||||
if (!ra->resp_hdrs) {
|
||||
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP response headers"));
|
||||
free(hd->hd_sd);
|
||||
free(hd->hd_calls);
|
||||
free(hd);
|
||||
return NULL;
|
||||
}
|
||||
hd->err_handler_fns = calloc(HTTPD_ERR_CODE_MAX, sizeof(httpd_err_handler_func_t));
|
||||
if (!hd->err_handler_fns) {
|
||||
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP error handlers"));
|
||||
free(ra->resp_hdrs);
|
||||
free(hd->hd_sd);
|
||||
free(hd->hd_calls);
|
||||
free(hd);
|
||||
return NULL;
|
||||
}
|
||||
/* Save the configuration for this instance */
|
||||
hd->config = *config;
|
||||
return hd;
|
||||
}
|
||||
|
||||
static void httpd_delete(struct httpd_data *hd)
|
||||
{
|
||||
struct httpd_req_aux *ra = &hd->hd_req_aux;
|
||||
/* Free memory of httpd instance data */
|
||||
free(hd->err_handler_fns);
|
||||
free(ra->resp_hdrs);
|
||||
free(hd->hd_sd);
|
||||
if (hd->ctrl_sock_semaphore) {
|
||||
vSemaphoreDelete(hd->ctrl_sock_semaphore);
|
||||
hd->ctrl_sock_semaphore = NULL;
|
||||
}
|
||||
|
||||
/* Free registered URI handlers */
|
||||
httpd_unregister_all_uri_handlers(hd);
|
||||
free(hd->hd_calls);
|
||||
free(hd);
|
||||
}
|
||||
|
||||
esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
|
||||
{
|
||||
if (handle == NULL || config == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Sanity check about whether LWIP is configured for providing the
|
||||
* maximum number of open sockets sufficient for the server. Though,
|
||||
* this check doesn't guarantee that many sockets will actually be
|
||||
* available at runtime as other processes may use up some sockets.
|
||||
* Note that server also uses 3 sockets for its internal use :
|
||||
* 1) listening for new TCP connections
|
||||
* 2) for sending control messages over UDP
|
||||
* 3) for receiving control messages over UDP
|
||||
* So the total number of required sockets is max_open_sockets + 3
|
||||
*/
|
||||
if (HTTPD_MAX_SOCKETS < config->max_open_sockets + 3) {
|
||||
ESP_LOGE(TAG, "Config option max_open_sockets is too large (max allowed %d, 3 sockets used by HTTP server internally)\n\t"
|
||||
"Either decrease this or configure LWIP_MAX_SOCKETS to a larger value",
|
||||
HTTPD_MAX_SOCKETS - 3);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_data *hd = httpd_create(config);
|
||||
if (hd == NULL) {
|
||||
/* Failed to allocate memory */
|
||||
return ESP_ERR_HTTPD_ALLOC_MEM;
|
||||
}
|
||||
/* Counting semaphore sized to the UDP control-socket recv mbox. Each
|
||||
* httpd_queue_work() take reserves one mbox slot; httpd_process_ctrl_msg()
|
||||
* gives one back per drain. This bounds the producer to the mbox capacity
|
||||
* and prevents silent lwIP-mbox overflow drops that would otherwise leak
|
||||
* the caller's async-send context. Always created so the default
|
||||
* (non-blocking) httpd_queue_work() path can also rely on it. */
|
||||
hd->ctrl_sock_semaphore = xSemaphoreCreateCounting(CONFIG_LWIP_UDP_RECVMBOX_SIZE, CONFIG_LWIP_UDP_RECVMBOX_SIZE);
|
||||
if (hd->ctrl_sock_semaphore == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create Semaphore");
|
||||
httpd_delete(hd);
|
||||
return ESP_ERR_HTTPD_ALLOC_MEM;
|
||||
}
|
||||
|
||||
if (httpd_server_init(hd) != ESP_OK) {
|
||||
httpd_delete(hd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
httpd_sess_init(hd);
|
||||
if (httpd_os_thread_create(&hd->hd_td.handle, "httpd",
|
||||
hd->config.stack_size,
|
||||
hd->config.task_priority,
|
||||
httpd_thread, hd,
|
||||
hd->config.core_id,
|
||||
hd->config.task_caps) != ESP_OK) {
|
||||
/* Close the open socket */
|
||||
close(hd->listen_fd);
|
||||
/* Close the control socket */
|
||||
cs_free_ctrl_sock(hd->ctrl_fd);
|
||||
/* Close the message socket */
|
||||
close(hd->msg_fd);
|
||||
/* Failed to launch task */
|
||||
httpd_delete(hd);
|
||||
return ESP_ERR_HTTPD_TASK;
|
||||
}
|
||||
|
||||
*handle = (httpd_handle_t)hd;
|
||||
hd->http_server_state = HTTP_SERVER_EVENT_START;
|
||||
esp_http_server_dispatch_event(HTTP_SERVER_EVENT_START, NULL, 0);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t httpd_stop(httpd_handle_t handle)
|
||||
{
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
if (hd == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_ctrl_data msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.hc_msg = HTTPD_CTRL_SHUTDOWN;
|
||||
|
||||
/* Reserve a slot in the ctrl mbox before sending so we never push past
|
||||
* its capacity. Blocking is safe: the httpd task is the consumer and
|
||||
* keeps draining the mbox until it observes HTTPD_CTRL_SHUTDOWN. */
|
||||
if (xSemaphoreTake(hd->ctrl_sock_semaphore, portMAX_DELAY) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "Failed to acquire ctrl socket semaphore");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
int ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to send shutdown signal err=%d", ret);
|
||||
xSemaphoreGive(hd->ctrl_sock_semaphore);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("sent control msg to stop server"));
|
||||
while (hd->hd_td.status != THREAD_STOPPED) {
|
||||
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"));
|
||||
httpd_delete(hd);
|
||||
esp_http_server_dispatch_event(HTTP_SERVER_EVENT_STOP, NULL, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_http_server_event_id_t httpd_get_server_state(httpd_handle_t handle)
|
||||
{
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
if (hd == NULL) {
|
||||
return HTTP_SERVER_EVENT_ERROR;
|
||||
}
|
||||
return hd->http_server_state;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,520 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include "esp_httpd_priv.h"
|
||||
|
||||
static const char *TAG = "httpd_sess";
|
||||
|
||||
typedef enum {
|
||||
HTTPD_TASK_NONE = 0,
|
||||
HTTPD_TASK_INIT, // Init session
|
||||
HTTPD_TASK_GET_ACTIVE, // Get active session (fd!=-1)
|
||||
HTTPD_TASK_GET_FREE, // Get free session slot (fd<0)
|
||||
HTTPD_TASK_FIND_FD, // Find session with specific fd
|
||||
HTTPD_TASK_SET_DESCRIPTOR, // Set descriptor
|
||||
HTTPD_TASK_DELETE_INVALID, // Delete invalid session
|
||||
HTTPD_TASK_FIND_LOWEST_LRU, // Find session with lowest lru
|
||||
HTTPD_TASK_CLOSE // Close session
|
||||
} task_t;
|
||||
|
||||
typedef struct {
|
||||
task_t task;
|
||||
int fd;
|
||||
fd_set *fdset;
|
||||
int max_fd;
|
||||
struct httpd_data *hd;
|
||||
uint64_t lru_counter;
|
||||
struct sock_db *session;
|
||||
} enum_context_t;
|
||||
|
||||
void httpd_sess_enum(struct httpd_data *hd, httpd_session_enum_function enum_function, void *context)
|
||||
{
|
||||
if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sock_db *current = hd->hd_sd;
|
||||
struct sock_db *end = hd->hd_sd + hd->config.max_open_sockets - 1;
|
||||
|
||||
while (current <= end) {
|
||||
if (enum_function && (!enum_function(current, context))) {
|
||||
break;
|
||||
}
|
||||
current++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a FD is valid
|
||||
static int fd_is_valid(int fd)
|
||||
{
|
||||
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
|
||||
}
|
||||
|
||||
static int enum_function(struct sock_db *session, void *context)
|
||||
{
|
||||
if ((!session) || (!context)) {
|
||||
return 0;
|
||||
}
|
||||
enum_context_t *ctx = (enum_context_t *) context;
|
||||
int found = 0;
|
||||
switch (ctx->task) {
|
||||
// Initialize session
|
||||
case HTTPD_TASK_INIT:
|
||||
session->fd = -1;
|
||||
session->ctx = NULL;
|
||||
session->for_async_req = false;
|
||||
break;
|
||||
// Get active session
|
||||
case HTTPD_TASK_GET_ACTIVE:
|
||||
found = (session->fd != -1);
|
||||
break;
|
||||
// Get free slot
|
||||
case HTTPD_TASK_GET_FREE:
|
||||
found = (session->fd < 0);
|
||||
break;
|
||||
// Find fd
|
||||
case HTTPD_TASK_FIND_FD:
|
||||
found = (session->fd == ctx->fd);
|
||||
break;
|
||||
// Set descriptor
|
||||
case HTTPD_TASK_SET_DESCRIPTOR:
|
||||
if (session->fd != -1 && !session->for_async_req) {
|
||||
FD_SET(session->fd, ctx->fdset);
|
||||
if (session->fd > ctx->max_fd) {
|
||||
ctx->max_fd = session->fd;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Delete invalid session
|
||||
case HTTPD_TASK_DELETE_INVALID:
|
||||
if (!fd_is_valid(session->fd)) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), session->fd);
|
||||
httpd_sess_delete(ctx->hd, session);
|
||||
}
|
||||
break;
|
||||
// Find lowest lru
|
||||
case HTTPD_TASK_FIND_LOWEST_LRU:
|
||||
// Found free slot - no need to check other sessions
|
||||
if (session->fd == -1) {
|
||||
return 0;
|
||||
}
|
||||
// Only close sockets that are not in use
|
||||
if (session->for_async_req == false) {
|
||||
// Check/update lowest lru
|
||||
if (session->lru_counter < ctx->lru_counter) {
|
||||
ctx->lru_counter = session->lru_counter;
|
||||
ctx->session = session;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HTTPD_TASK_CLOSE:
|
||||
if (session->fd != -1) {
|
||||
ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), session->fd);
|
||||
httpd_sess_delete(ctx->hd, session);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
if (found) {
|
||||
ctx->session = session;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void httpd_sess_close(void *arg)
|
||||
{
|
||||
struct sock_db *sock_db = (struct sock_db *) arg;
|
||||
if (!sock_db) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sock_db->lru_counter && !sock_db->lru_socket) {
|
||||
ESP_LOGD(TAG, "Skipping session close for %d as it seems to be a race condition", sock_db->fd);
|
||||
return;
|
||||
}
|
||||
sock_db->lru_socket = false;
|
||||
struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
|
||||
hd->http_server_state = HTTP_SERVER_EVENT_DISCONNECTED;
|
||||
httpd_sess_delete(hd, sock_db);
|
||||
}
|
||||
|
||||
struct sock_db *httpd_sess_get_free(struct httpd_data *hd)
|
||||
{
|
||||
if ((!hd) || (hd->hd_sd_active_count == hd->config.max_open_sockets)) {
|
||||
return NULL;
|
||||
}
|
||||
enum_context_t context = {
|
||||
.task = HTTPD_TASK_GET_FREE
|
||||
};
|
||||
httpd_sess_enum(hd, enum_function, &context);
|
||||
return context.session;
|
||||
}
|
||||
|
||||
bool httpd_is_sess_available(struct httpd_data *hd)
|
||||
{
|
||||
return httpd_sess_get_free(hd) ? true : false;
|
||||
}
|
||||
|
||||
struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
|
||||
{
|
||||
if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Check if called inside a request handler, and the session sockfd in use is same as the parameter
|
||||
// => Just return the pointer to the sock_db corresponding to the request
|
||||
if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
|
||||
return hd->hd_req_aux.sd;
|
||||
}
|
||||
|
||||
enum_context_t context = {
|
||||
.task = HTTPD_TASK_FIND_FD,
|
||||
.fd = sockfd
|
||||
};
|
||||
httpd_sess_enum(hd, enum_function, &context);
|
||||
return context.session;
|
||||
}
|
||||
|
||||
esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
|
||||
{
|
||||
ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd);
|
||||
|
||||
if (httpd_sess_get(hd, newfd)) {
|
||||
ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
struct sock_db *session = httpd_sess_get_free(hd);
|
||||
if (!session) {
|
||||
ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Clear session data
|
||||
memset(session, 0, sizeof (struct sock_db));
|
||||
session->fd = newfd;
|
||||
session->handle = (httpd_handle_t) hd;
|
||||
session->send_fn = httpd_default_send;
|
||||
session->recv_fn = httpd_default_recv;
|
||||
session->lru_counter = hd->lru_counter;
|
||||
|
||||
// increment number of sessions
|
||||
hd->hd_sd_active_count++;
|
||||
|
||||
// Call user-defined session opening function
|
||||
if (hd->config.open_fn) {
|
||||
esp_err_t ret = hd->config.open_fn(hd, session->fd);
|
||||
if (ret != ESP_OK) {
|
||||
httpd_sess_delete(hd, session);
|
||||
ESP_LOGD(TAG, LOG_FMT("open_fn failed for fd = %d"), newfd);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void httpd_sess_free_ctx(void **ctx, httpd_free_ctx_fn_t free_fn)
|
||||
{
|
||||
if ((!ctx) || (!*ctx)) {
|
||||
return;
|
||||
}
|
||||
if (free_fn) {
|
||||
free_fn(*ctx);
|
||||
} else {
|
||||
free(*ctx);
|
||||
}
|
||||
*ctx = NULL;
|
||||
}
|
||||
|
||||
void httpd_sess_clear_ctx(struct sock_db *session)
|
||||
{
|
||||
if ((!session) || ((!session->ctx) && (!session->transport_ctx))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// free user ctx
|
||||
if (session->ctx) {
|
||||
httpd_sess_free_ctx(&session->ctx, session->free_ctx);
|
||||
session->free_ctx = NULL;
|
||||
}
|
||||
|
||||
// Free 'transport' context
|
||||
if (session->transport_ctx) {
|
||||
httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
|
||||
session->free_transport_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
|
||||
{
|
||||
struct sock_db *session = httpd_sess_get(handle, sockfd);
|
||||
if (!session) {
|
||||
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 == session) {
|
||||
return hd->hd_req.sess_ctx;
|
||||
}
|
||||
return session->ctx;
|
||||
}
|
||||
|
||||
void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
|
||||
{
|
||||
struct sock_db *session = httpd_sess_get(handle, sockfd);
|
||||
if (!session) {
|
||||
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 == session) {
|
||||
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 (session->ctx != hd->hd_req.sess_ctx) {
|
||||
httpd_sess_free_ctx(&hd->hd_req.sess_ctx, hd->hd_req.free_ctx); // Free previous context
|
||||
}
|
||||
hd->hd_req.sess_ctx = ctx;
|
||||
}
|
||||
hd->hd_req.free_ctx = free_fn;
|
||||
return;
|
||||
}
|
||||
|
||||
// Else set the context inside the sock_db structure
|
||||
if (session->ctx != ctx) {
|
||||
// Free previous context
|
||||
httpd_sess_free_ctx(&session->ctx, session->free_ctx);
|
||||
session->ctx = ctx;
|
||||
}
|
||||
session->free_ctx = free_fn;
|
||||
}
|
||||
|
||||
void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
|
||||
{
|
||||
struct sock_db *session = httpd_sess_get(handle, sockfd);
|
||||
return session ? session->transport_ctx : NULL;
|
||||
}
|
||||
|
||||
void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
|
||||
{
|
||||
struct sock_db *session = httpd_sess_get(handle, sockfd);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->transport_ctx != ctx) {
|
||||
// Free previous transport context
|
||||
httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
|
||||
session->transport_ctx = ctx;
|
||||
}
|
||||
session->free_transport_ctx = free_fn;
|
||||
}
|
||||
|
||||
void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd)
|
||||
{
|
||||
enum_context_t context = {
|
||||
.task = HTTPD_TASK_SET_DESCRIPTOR,
|
||||
.max_fd = -1,
|
||||
.fdset = fdset
|
||||
};
|
||||
httpd_sess_enum(hd, enum_function, &context);
|
||||
if (maxfd) {
|
||||
*maxfd = context.max_fd;
|
||||
}
|
||||
}
|
||||
|
||||
void httpd_sess_delete_invalid(struct httpd_data *hd)
|
||||
{
|
||||
enum_context_t context = {
|
||||
.task = HTTPD_TASK_DELETE_INVALID,
|
||||
.hd = hd
|
||||
};
|
||||
httpd_sess_enum(hd, enum_function, &context);
|
||||
}
|
||||
|
||||
void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session)
|
||||
{
|
||||
if ((!hd) || (!session) || (session->fd < 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("fd = %d"), session->fd);
|
||||
if (hd->config.enable_so_linger) {
|
||||
struct linger so_linger = {
|
||||
.l_onoff = true,
|
||||
.l_linger = hd->config.linger_timeout,
|
||||
};
|
||||
if (setsockopt(session->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(struct linger)) < 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("error enabling SO_LINGER (%d)"), errno);
|
||||
}
|
||||
}
|
||||
|
||||
// Call close function if defined
|
||||
if (hd->config.close_fn) {
|
||||
hd->config.close_fn(hd, session->fd);
|
||||
} else {
|
||||
close(session->fd);
|
||||
}
|
||||
hd->http_server_state = HTTP_SERVER_EVENT_DISCONNECTED;
|
||||
esp_http_server_dispatch_event(HTTP_SERVER_EVENT_DISCONNECTED, &session->fd, sizeof(int));
|
||||
|
||||
// clear all contexts
|
||||
httpd_sess_clear_ctx(session);
|
||||
|
||||
// mark session slot as available
|
||||
session->fd = -1;
|
||||
|
||||
// decrement number of sessions
|
||||
hd->hd_sd_active_count--;
|
||||
ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
|
||||
if (!hd->hd_sd_active_count) {
|
||||
hd->lru_counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void httpd_sess_init(struct httpd_data *hd)
|
||||
{
|
||||
enum_context_t context = {
|
||||
.task = HTTPD_TASK_INIT
|
||||
};
|
||||
httpd_sess_enum(hd, enum_function, &context);
|
||||
}
|
||||
|
||||
bool httpd_sess_pending(struct httpd_data *hd, struct sock_db *session)
|
||||
{
|
||||
if (!session) {
|
||||
return false;
|
||||
}
|
||||
if (session->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 (session->pending_fn(hd, session->fd) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return (session->pending_len != 0);
|
||||
}
|
||||
|
||||
/* This MUST return ESP_OK on successful execution. If any other
|
||||
* value is returned, everything related to this socket will be
|
||||
* cleaned up and the socket will be closed.
|
||||
*/
|
||||
esp_err_t httpd_sess_process(struct httpd_data *hd, struct sock_db *session)
|
||||
{
|
||||
if ((!hd) || (!session)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
|
||||
if (httpd_req_new(hd, session) != ESP_OK) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
|
||||
if (httpd_req_delete(hd) != ESP_OK) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("success"));
|
||||
session->lru_counter = ++hd->lru_counter;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
|
||||
enum_context_t context = {
|
||||
.task = HTTPD_TASK_FIND_FD,
|
||||
.fd = sockfd
|
||||
};
|
||||
httpd_sess_enum(hd, enum_function, &context);
|
||||
if (context.session) {
|
||||
context.session->lru_counter = ++hd->lru_counter;
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
|
||||
{
|
||||
enum_context_t context = {
|
||||
.task = HTTPD_TASK_FIND_LOWEST_LRU,
|
||||
.lru_counter = UINT64_MAX,
|
||||
.fd = -1
|
||||
};
|
||||
httpd_sess_enum(hd, enum_function, &context);
|
||||
if (!context.session) {
|
||||
return ESP_OK;
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("Closing session with fd %d"), context.session->fd);
|
||||
context.session->lru_socket = true;
|
||||
return httpd_sess_trigger_close_(hd, context.session);
|
||||
}
|
||||
|
||||
esp_err_t httpd_sess_trigger_close_(httpd_handle_t handle, struct sock_db *session)
|
||||
{
|
||||
if (!session) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
return httpd_queue_work(handle, httpd_sess_close, session);
|
||||
}
|
||||
|
||||
esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
|
||||
{
|
||||
struct sock_db *session = httpd_sess_get(handle, sockfd);
|
||||
if (!session) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
return httpd_sess_trigger_close_(handle, session);
|
||||
}
|
||||
|
||||
void httpd_sess_close_all(struct httpd_data *hd)
|
||||
{
|
||||
enum_context_t context = {
|
||||
.task = HTTPD_TASK_CLOSE,
|
||||
.hd = hd
|
||||
};
|
||||
httpd_sess_enum(hd, enum_function, &context);
|
||||
}
|
||||
|
||||
esp_err_t httpd_sess_close_lru_direct(struct httpd_data *hd)
|
||||
{
|
||||
enum_context_t context = {
|
||||
.task = HTTPD_TASK_FIND_LOWEST_LRU,
|
||||
.lru_counter = UINT64_MAX,
|
||||
.fd = -1
|
||||
};
|
||||
httpd_sess_enum(hd, enum_function, &context);
|
||||
if (!context.session) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("Directly closing session with fd %d"), context.session->fd);
|
||||
// Call httpd_sess_delete directly instead of going through work queue
|
||||
httpd_sess_delete(hd, context.session);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,848 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
#include <errno.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include "esp_httpd_priv.h"
|
||||
#include <netinet/tcp.h>
|
||||
#include "ctrl_sock.h"
|
||||
|
||||
static const char *TAG = "httpd_txrx";
|
||||
|
||||
esp_err_t httpd_sess_set_send_override(httpd_handle_t hd, int sockfd, httpd_send_func_t send_func)
|
||||
{
|
||||
struct sock_db *sess = httpd_sess_get(hd, sockfd);
|
||||
if (!sess) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
sess->send_fn = send_func;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t httpd_sess_set_recv_override(httpd_handle_t hd, int sockfd, httpd_recv_func_t recv_func)
|
||||
{
|
||||
struct sock_db *sess = httpd_sess_get(hd, sockfd);
|
||||
if (!sess) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
sess->recv_fn = recv_func;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
sess->pending_fn = pending_func;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len)
|
||||
{
|
||||
if (r == NULL || buf == NULL) {
|
||||
return HTTPD_SOCK_ERR_INVALID;
|
||||
}
|
||||
|
||||
if (!httpd_valid_req(r)) {
|
||||
return HTTPD_SOCK_ERR_INVALID;
|
||||
}
|
||||
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
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;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len)
|
||||
{
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
int ret;
|
||||
|
||||
while (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;
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("sent = %d"), ret);
|
||||
buf += ret;
|
||||
buf_len -= ret;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static size_t httpd_recv_pending(httpd_req_t *r, char *buf, size_t buf_len)
|
||||
{
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
size_t offset = sizeof(ra->sd->pending_data) - ra->sd->pending_len;
|
||||
|
||||
/* buf_len must not be greater than remaining_len */
|
||||
buf_len = MIN(ra->sd->pending_len, buf_len);
|
||||
memcpy(buf, ra->sd->pending_data + offset, buf_len);
|
||||
|
||||
ra->sd->pending_len -= buf_len;
|
||||
return buf_len;
|
||||
}
|
||||
|
||||
int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t buf_len, httpd_recv_opt_t opt)
|
||||
{
|
||||
ESP_LOGD(TAG, LOG_FMT("requested length = %"NEWLIB_NANO_COMPAT_FORMAT), NEWLIB_NANO_COMPAT_CAST(buf_len));
|
||||
|
||||
size_t pending_len = 0;
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
|
||||
/* First fetch pending data from local buffer */
|
||||
if (ra->sd->pending_len > 0) {
|
||||
ESP_LOGD(TAG, LOG_FMT("pending length = %"NEWLIB_NANO_COMPAT_FORMAT), NEWLIB_NANO_COMPAT_CAST(ra->sd->pending_len));
|
||||
pending_len = httpd_recv_pending(r, buf, buf_len);
|
||||
buf += pending_len;
|
||||
buf_len -= pending_len;
|
||||
|
||||
/* If buffer filled then no need to recv.
|
||||
* If asked to halt after receiving pending data then
|
||||
* return with received length */
|
||||
if (!buf_len || opt == HTTPD_RECV_OPT_HALT_AFTER_PENDING) {
|
||||
return pending_len;
|
||||
}
|
||||
}
|
||||
|
||||
/* Receive data of remaining length */
|
||||
size_t recv_len = pending_len;
|
||||
do {
|
||||
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)) {
|
||||
/* If recv() timeout occurred, but pending data is
|
||||
* present, return length of pending data.
|
||||
* This behavior is similar to that of socket recv()
|
||||
* function, which, in case has only partially read the
|
||||
* requested length, due to timeout, returns with read
|
||||
* length, rather than error */
|
||||
return pending_len;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
recv_len += ret;
|
||||
buf += ret;
|
||||
buf_len -= ret;
|
||||
} while (buf_len > 0 && opt == HTTPD_RECV_OPT_BLOCKING);
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("received length = %"NEWLIB_NANO_COMPAT_FORMAT), NEWLIB_NANO_COMPAT_CAST(recv_len));
|
||||
return recv_len;
|
||||
}
|
||||
|
||||
int httpd_recv(httpd_req_t *r, char *buf, size_t buf_len)
|
||||
{
|
||||
return httpd_recv_with_opt(r, buf, buf_len, HTTPD_RECV_OPT_NONE);
|
||||
}
|
||||
|
||||
size_t httpd_unrecv(struct httpd_req *r, const char *buf, size_t buf_len)
|
||||
{
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
/* Truncate if external buf_len is greater than pending_data buffer size */
|
||||
ra->sd->pending_len = MIN(sizeof(ra->sd->pending_data), buf_len);
|
||||
|
||||
/* Copy data into internal pending_data buffer with the exact offset
|
||||
* such that it is right aligned inside the buffer */
|
||||
size_t offset = sizeof(ra->sd->pending_data) - ra->sd->pending_len;
|
||||
memcpy(ra->sd->pending_data + offset, buf, ra->sd->pending_len);
|
||||
ESP_LOGD(TAG, LOG_FMT("length = %"NEWLIB_NANO_COMPAT_FORMAT), NEWLIB_NANO_COMPAT_CAST(ra->sd->pending_len));
|
||||
return ra->sd->pending_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* This API appends an additional header field-value pair in the HTTP response.
|
||||
* But the header isn't sent out until any of the send APIs is executed.
|
||||
*/
|
||||
esp_err_t httpd_resp_set_hdr(httpd_req_t *r, const char *field, const char *value)
|
||||
{
|
||||
if (r == NULL || field == NULL || value == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!httpd_valid_req(r)) {
|
||||
return ESP_ERR_HTTPD_INVALID_REQ;
|
||||
}
|
||||
|
||||
/* Reject CRLF in header field or value */
|
||||
if (strpbrk(field, "\r\n") || strpbrk(value, "\r\n")) {
|
||||
ESP_LOGW(TAG, LOG_FMT("rejecting header with CRLF: %.32s"), field);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
struct httpd_data *hd = (struct httpd_data *) r->handle;
|
||||
|
||||
/* Number of additional headers is limited */
|
||||
if (ra->resp_hdrs_count >= hd->config.max_resp_headers) {
|
||||
return ESP_ERR_HTTPD_RESP_HDR;
|
||||
}
|
||||
|
||||
/* Assign header field-value pair */
|
||||
ra->resp_hdrs[ra->resp_hdrs_count].field = field;
|
||||
ra->resp_hdrs[ra->resp_hdrs_count].value = value;
|
||||
ra->resp_hdrs_count++;
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("new header = %s: %s"), field, value);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* This API sets the status of the HTTP response to the value specified.
|
||||
* But the status isn't sent out until any of the send APIs is executed.
|
||||
*/
|
||||
esp_err_t httpd_resp_set_status(httpd_req_t *r, const char *status)
|
||||
{
|
||||
if (r == NULL || status == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!httpd_valid_req(r)) {
|
||||
return ESP_ERR_HTTPD_INVALID_REQ;
|
||||
}
|
||||
|
||||
/* Reject CRLF in status */
|
||||
if (strpbrk(status, "\r\n")) {
|
||||
ESP_LOGW(TAG, LOG_FMT("rejecting status with CRLF: %.32s"), status);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
ra->status = (char *)status;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* This API sets the method/type of the HTTP response to the value specified.
|
||||
* But the method isn't sent out until any of the send APIs is executed.
|
||||
*/
|
||||
esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type)
|
||||
{
|
||||
if (r == NULL || type == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!httpd_valid_req(r)) {
|
||||
return ESP_ERR_HTTPD_INVALID_REQ;
|
||||
}
|
||||
|
||||
/* Reject CRLF in content type */
|
||||
if (strpbrk(type, "\r\n")) {
|
||||
ESP_LOGW(TAG, LOG_FMT("rejecting content type with CRLF: %.32s"), type);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
ra->content_type = (char *)type;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!httpd_valid_req(r)) {
|
||||
return ESP_ERR_HTTPD_INVALID_REQ;
|
||||
}
|
||||
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
const char *httpd_hdr_str = "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n";
|
||||
const char *colon_separator = ": ";
|
||||
const char *cr_lf_seperator = "\r\n";
|
||||
|
||||
if (buf_len == HTTPD_RESP_USE_STRLEN) {
|
||||
buf_len = strlen(buf);
|
||||
}
|
||||
|
||||
/* Request headers are no longer available */
|
||||
ra->req_hdrs_count = 0;
|
||||
|
||||
/* Calculate the size of the headers. +1 for the null terminator */
|
||||
size_t required_size = snprintf(NULL, 0, httpd_hdr_str, ra->status, ra->content_type, buf_len) + 1;
|
||||
if (required_size > ra->max_req_hdr_len) {
|
||||
return ESP_ERR_HTTPD_RESP_HDR;
|
||||
}
|
||||
char *res_buf = malloc(required_size); /* Temporary buffer to store the headers */
|
||||
if (res_buf == NULL) {
|
||||
ESP_LOGE(TAG, "Unable to allocate httpd send buffer");
|
||||
return ESP_ERR_HTTPD_ALLOC_MEM;
|
||||
}
|
||||
|
||||
esp_err_t ret = snprintf(res_buf, required_size, httpd_hdr_str, ra->status, ra->content_type, buf_len);
|
||||
if (ret < 0 || ret >= required_size) {
|
||||
free(res_buf);
|
||||
return ESP_ERR_HTTPD_RESP_HDR;
|
||||
}
|
||||
ESP_LOGD(TAG, "httpd send buffer size = %d", strlen(res_buf));
|
||||
ret = httpd_send_all(r, res_buf, strlen(res_buf));
|
||||
free(res_buf);
|
||||
if (ret != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
|
||||
/* Sending additional headers based on set_header */
|
||||
for (unsigned i = 0; i < ra->resp_hdrs_count; i++) {
|
||||
/* Send header field */
|
||||
if (httpd_send_all(r, ra->resp_hdrs[i].field, strlen(ra->resp_hdrs[i].field)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
/* Send ': ' */
|
||||
if (httpd_send_all(r, colon_separator, strlen(colon_separator)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
/* Send header value */
|
||||
if (httpd_send_all(r, ra->resp_hdrs[i].value, strlen(ra->resp_hdrs[i].value)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
/* Send CR + LF */
|
||||
if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
}
|
||||
|
||||
/* End header section */
|
||||
if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
struct httpd_data *hd = (struct httpd_data *) r->handle;
|
||||
hd->http_server_state = HTTP_SERVER_EVENT_HEADERS_SENT;
|
||||
esp_http_server_dispatch_event(HTTP_SERVER_EVENT_HEADERS_SENT, &(ra->sd->fd), sizeof(int));
|
||||
|
||||
/* Sending content */
|
||||
if (buf && buf_len) {
|
||||
if (httpd_send_all(r, buf, buf_len) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
}
|
||||
esp_http_server_event_data evt_data = {
|
||||
.fd = ra->sd->fd,
|
||||
.data_len = buf_len,
|
||||
};
|
||||
hd->http_server_state = HTTP_SERVER_EVENT_SENT_DATA;
|
||||
esp_http_server_dispatch_event(HTTP_SERVER_EVENT_SENT_DATA, &evt_data, sizeof(esp_http_server_event_data));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!httpd_valid_req(r)) {
|
||||
return ESP_ERR_HTTPD_INVALID_REQ;
|
||||
}
|
||||
|
||||
if (buf_len == HTTPD_RESP_USE_STRLEN) {
|
||||
buf_len = strlen(buf);
|
||||
}
|
||||
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
struct httpd_data *hd = (struct httpd_data *) r->handle;
|
||||
const char *httpd_chunked_hdr_str = "HTTP/1.1 %s\r\nContent-Type: %s\r\nTransfer-Encoding: chunked\r\n";
|
||||
const char *colon_separator = ": ";
|
||||
const char *cr_lf_seperator = "\r\n";
|
||||
|
||||
/* Request headers are no longer available */
|
||||
ra->req_hdrs_count = 0;
|
||||
|
||||
if (!ra->first_chunk_sent) {
|
||||
/* Calculate the size of the headers. +1 for the null terminator */
|
||||
size_t required_size = snprintf(NULL, 0, httpd_chunked_hdr_str, ra->status, ra->content_type) + 1;
|
||||
if (required_size > ra->max_req_hdr_len) {
|
||||
return ESP_ERR_HTTPD_RESP_HDR;
|
||||
}
|
||||
char *res_buf = malloc(required_size); /* Temporary buffer to store the headers */
|
||||
if (res_buf == NULL) {
|
||||
ESP_LOGE(TAG, "Unable to allocate httpd send chunk buffer");
|
||||
return ESP_ERR_HTTPD_ALLOC_MEM;
|
||||
}
|
||||
esp_err_t ret = snprintf(res_buf, required_size, httpd_chunked_hdr_str, ra->status, ra->content_type);
|
||||
if (ret < 0 || ret >= required_size) {
|
||||
free(res_buf);
|
||||
return ESP_ERR_HTTPD_RESP_HDR;
|
||||
}
|
||||
ESP_LOGD(TAG, "httpd send chunk buffer size = %d", strlen(res_buf));
|
||||
/* Size of essential headers is limited by scratch buffer size */
|
||||
ret = httpd_send_all(r, res_buf, strlen(res_buf));
|
||||
free(res_buf);
|
||||
if (ret != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
|
||||
/* Sending additional headers based on set_header */
|
||||
for (unsigned i = 0; i < ra->resp_hdrs_count; i++) {
|
||||
/* Send header field */
|
||||
if (httpd_send_all(r, ra->resp_hdrs[i].field, strlen(ra->resp_hdrs[i].field)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
/* Send ': ' */
|
||||
if (httpd_send_all(r, colon_separator, strlen(colon_separator)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
/* Send header value */
|
||||
if (httpd_send_all(r, ra->resp_hdrs[i].value, strlen(ra->resp_hdrs[i].value)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
/* Send CR + LF */
|
||||
if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
}
|
||||
|
||||
/* End header section */
|
||||
if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
ra->first_chunk_sent = true;
|
||||
}
|
||||
|
||||
/* Sending chunked content */
|
||||
char len_str[10];
|
||||
snprintf(len_str, sizeof(len_str), "%lx\r\n", (long)buf_len);
|
||||
if (httpd_send_all(r, len_str, strlen(len_str)) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
|
||||
if (buf) {
|
||||
if (httpd_send_all(r, buf, (size_t) buf_len) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
}
|
||||
|
||||
/* Indicate end of chunk */
|
||||
if (httpd_send_all(r, "\r\n", strlen("\r\n")) != ESP_OK) {
|
||||
return ESP_ERR_HTTPD_RESP_SEND;
|
||||
}
|
||||
esp_http_server_event_data evt_data = {
|
||||
.fd = ra->sd->fd,
|
||||
.data_len = buf_len,
|
||||
};
|
||||
hd->http_server_state = HTTP_SERVER_EVENT_SENT_DATA;
|
||||
esp_http_server_dispatch_event(HTTP_SERVER_EVENT_SENT_DATA, &evt_data, sizeof(esp_http_server_event_data));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t httpd_resp_send_err(httpd_req_t *req, httpd_err_code_t error, const char *usr_msg)
|
||||
{
|
||||
esp_err_t ret;
|
||||
const char *msg;
|
||||
const char *status;
|
||||
|
||||
switch (error) {
|
||||
case HTTPD_501_METHOD_NOT_IMPLEMENTED:
|
||||
status = "501 Method Not Implemented";
|
||||
msg = "Server does not support this method";
|
||||
break;
|
||||
case HTTPD_505_VERSION_NOT_SUPPORTED:
|
||||
status = "505 Version Not Supported";
|
||||
msg = "HTTP version not supported by server";
|
||||
break;
|
||||
case HTTPD_400_BAD_REQUEST:
|
||||
status = "400 Bad Request";
|
||||
msg = "Bad request syntax";
|
||||
break;
|
||||
case HTTPD_401_UNAUTHORIZED:
|
||||
status = "401 Unauthorized";
|
||||
msg = "No permission -- see authorization schemes";
|
||||
break;
|
||||
case HTTPD_403_FORBIDDEN:
|
||||
status = "403 Forbidden";
|
||||
msg = "Request forbidden -- authorization will not help";
|
||||
break;
|
||||
case HTTPD_404_NOT_FOUND:
|
||||
status = "404 Not Found";
|
||||
msg = "Nothing matches the given URI";
|
||||
break;
|
||||
case HTTPD_405_METHOD_NOT_ALLOWED:
|
||||
status = "405 Method Not Allowed";
|
||||
msg = "Specified method is invalid for this resource";
|
||||
break;
|
||||
case HTTPD_408_REQ_TIMEOUT:
|
||||
status = "408 Request Timeout";
|
||||
msg = "Server closed this connection";
|
||||
break;
|
||||
case HTTPD_414_URI_TOO_LONG:
|
||||
status = "414 URI Too Long";
|
||||
msg = "URI is too long";
|
||||
break;
|
||||
case HTTPD_411_LENGTH_REQUIRED:
|
||||
status = "411 Length Required";
|
||||
msg = "Client must specify Content-Length";
|
||||
break;
|
||||
case HTTPD_413_CONTENT_TOO_LARGE:
|
||||
status = "413 Content Too Large";
|
||||
msg = "Content is too large";
|
||||
break;
|
||||
case HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE:
|
||||
status = "431 Request Header Fields Too Large";
|
||||
msg = "Header fields are too long";
|
||||
break;
|
||||
case HTTPD_500_INTERNAL_SERVER_ERROR:
|
||||
default:
|
||||
status = "500 Internal Server Error";
|
||||
msg = "Server has encountered an unexpected error";
|
||||
}
|
||||
|
||||
/* If user has provided custom message, override default message */
|
||||
msg = usr_msg ? usr_msg : msg;
|
||||
ESP_LOGW(TAG, LOG_FMT("%s - %s"), status, msg);
|
||||
|
||||
/* Set error code in HTTP response */
|
||||
httpd_resp_set_status(req, status);
|
||||
httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
|
||||
|
||||
#ifdef CONFIG_HTTPD_ERR_RESP_NO_DELAY
|
||||
/* Use TCP_NODELAY option to force socket to send data in buffer
|
||||
* This ensures that the error message is sent before the socket
|
||||
* is closed */
|
||||
struct httpd_req_aux *ra = req->aux;
|
||||
int nodelay = 1;
|
||||
if (setsockopt(ra->sd->fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)) < 0) {
|
||||
/* If failed to turn on TCP_NODELAY, throw warning and continue */
|
||||
ESP_LOGW(TAG, LOG_FMT("error calling setsockopt : %d"), errno);
|
||||
nodelay = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Send HTTP error message */
|
||||
ret = httpd_resp_send(req, msg, HTTPD_RESP_USE_STRLEN);
|
||||
|
||||
#ifdef CONFIG_HTTPD_ERR_RESP_NO_DELAY
|
||||
/* If TCP_NODELAY was set successfully above, time to disable it */
|
||||
if (nodelay == 1) {
|
||||
nodelay = 0;
|
||||
if (setsockopt(ra->sd->fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)) < 0) {
|
||||
/* If failed to turn off TCP_NODELAY, throw error and
|
||||
* return failure to signal for socket closure */
|
||||
ESP_LOGE(TAG, LOG_FMT("error calling setsockopt : %d"), errno);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
esp_http_server_dispatch_event(HTTP_SERVER_EVENT_ERROR, &error, sizeof(httpd_err_code_t));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t httpd_resp_send_custom_err(httpd_req_t *req, const char *status, const char *msg)
|
||||
{
|
||||
ESP_LOGW(TAG, LOG_FMT("%s - %s"), status, msg);
|
||||
|
||||
/* Set error code in HTTP response */
|
||||
httpd_resp_set_status(req, status);
|
||||
httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
|
||||
|
||||
#ifdef CONFIG_HTTPD_ERR_RESP_NO_DELAY
|
||||
/* Use TCP_NODELAY option to force socket to send data in buffer
|
||||
* This ensures that the error message is sent before the socket
|
||||
* is closed */
|
||||
struct httpd_req_aux *ra = req->aux;
|
||||
int nodelay = 1;
|
||||
if (setsockopt(ra->sd->fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)) < 0) {
|
||||
/* If failed to turn on TCP_NODELAY, throw warning and continue */
|
||||
ESP_LOGW(TAG, LOG_FMT("error calling setsockopt : %d"), errno);
|
||||
nodelay = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Send HTTP error message */
|
||||
esp_err_t ret = httpd_resp_send(req, msg, HTTPD_RESP_USE_STRLEN);
|
||||
|
||||
#ifdef CONFIG_HTTPD_ERR_RESP_NO_DELAY
|
||||
/* If TCP_NODELAY was set successfully above, time to disable it */
|
||||
if (nodelay == 1) {
|
||||
nodelay = 0;
|
||||
if (setsockopt(ra->sd->fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)) < 0) {
|
||||
/* If failed to turn off TCP_NODELAY, throw error and
|
||||
* return failure to signal for socket closure */
|
||||
ESP_LOGE(TAG, LOG_FMT("error calling setsockopt : %d"), errno);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t httpd_register_err_handler(httpd_handle_t handle,
|
||||
httpd_err_code_t error,
|
||||
httpd_err_handler_func_t err_handler_fn)
|
||||
{
|
||||
if (handle == NULL || error >= HTTPD_ERR_CODE_MAX) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
hd->err_handler_fns[error] = err_handler_fn;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t httpd_req_handle_err(httpd_req_t *req, httpd_err_code_t error)
|
||||
{
|
||||
struct httpd_data *hd = (struct httpd_data *) req->handle;
|
||||
esp_err_t ret;
|
||||
|
||||
/* Invoke custom error handler if configured */
|
||||
if (hd->err_handler_fns[error]) {
|
||||
ret = hd->err_handler_fns[error](req, error);
|
||||
|
||||
/* If error code is 500, force return failure
|
||||
* irrespective of the handler's return value */
|
||||
ret = (error == HTTPD_500_INTERNAL_SERVER_ERROR ? ESP_FAIL : ret);
|
||||
} else {
|
||||
/* If no handler is registered for this error default
|
||||
* behavior is to send the HTTP error response and
|
||||
* return failure for closure of underlying socket */
|
||||
httpd_resp_send_err(req, error, NULL);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int httpd_req_recv(httpd_req_t *r, char *buf, size_t buf_len)
|
||||
{
|
||||
if (r == NULL || buf == NULL) {
|
||||
return HTTPD_SOCK_ERR_INVALID;
|
||||
}
|
||||
|
||||
if (!httpd_valid_req(r)) {
|
||||
ESP_LOGW(TAG, LOG_FMT("invalid request"));
|
||||
return HTTPD_SOCK_ERR_INVALID;
|
||||
}
|
||||
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
ESP_LOGD(TAG, LOG_FMT("remaining length = %"NEWLIB_NANO_COMPAT_FORMAT), NEWLIB_NANO_COMPAT_CAST(ra->remaining_len));
|
||||
|
||||
if (buf_len > ra->remaining_len) {
|
||||
buf_len = ra->remaining_len;
|
||||
}
|
||||
if (buf_len == 0) {
|
||||
return buf_len;
|
||||
}
|
||||
|
||||
int ret = httpd_recv(r, buf, buf_len);
|
||||
if (ret < 0) {
|
||||
ESP_LOGD(TAG, LOG_FMT("error in httpd_recv"));
|
||||
return ret;
|
||||
}
|
||||
ra->remaining_len -= ret;
|
||||
ESP_LOGD(TAG, LOG_FMT("received length = %d"), ret);
|
||||
struct httpd_data *hd = (struct httpd_data *) r->handle;
|
||||
hd->http_server_state = HTTP_SERVER_EVENT_ON_DATA;
|
||||
esp_http_server_event_data evt_data = {
|
||||
.fd = ra->sd->fd,
|
||||
.data_len = ret,
|
||||
};
|
||||
esp_http_server_dispatch_event(HTTP_SERVER_EVENT_ON_DATA, &evt_data, sizeof(esp_http_server_event_data));
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out)
|
||||
{
|
||||
if (r == NULL || out == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// alloc async req
|
||||
httpd_req_t *async = malloc(sizeof(httpd_req_t));
|
||||
if (async == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memcpy(async, r, sizeof(httpd_req_t));
|
||||
|
||||
// alloc async aux
|
||||
async->aux = malloc(sizeof(struct httpd_req_aux));
|
||||
if (async->aux == NULL) {
|
||||
free(async);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memcpy(async->aux, r->aux, sizeof(struct httpd_req_aux));
|
||||
|
||||
// Copy response header block
|
||||
struct httpd_data *hd = (struct httpd_data *) r->handle;
|
||||
struct httpd_req_aux *async_aux = (struct httpd_req_aux *) async->aux;
|
||||
struct httpd_req_aux *r_aux = (struct httpd_req_aux *) r->aux;
|
||||
|
||||
if (r_aux->scratch) {
|
||||
async_aux->scratch = malloc(r_aux->scratch_cur_size);
|
||||
if (async_aux->scratch == NULL) {
|
||||
free(async_aux);
|
||||
free(async);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memcpy(async_aux->scratch, r_aux->scratch, r_aux->scratch_cur_size);
|
||||
} else {
|
||||
async_aux->scratch = NULL;
|
||||
}
|
||||
|
||||
async_aux->resp_hdrs = calloc(hd->config.max_resp_headers, sizeof(struct resp_hdr));
|
||||
if (async_aux->resp_hdrs == NULL) {
|
||||
free(async_aux->scratch);
|
||||
free(async_aux);
|
||||
free(async);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memcpy(async_aux->resp_hdrs, r_aux->resp_hdrs, hd->config.max_resp_headers * sizeof(struct resp_hdr));
|
||||
|
||||
// Prevent the main thread from reading the rest of the request after the handler returns.
|
||||
r_aux->remaining_len = 0;
|
||||
|
||||
// mark socket as "in use"
|
||||
r_aux->sd->for_async_req = true;
|
||||
|
||||
*out = async;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t httpd_req_async_handler_complete(httpd_req_t *r)
|
||||
{
|
||||
if (r == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Get server handle and control socket info before freeing the request
|
||||
struct httpd_data *hd = (struct httpd_data *) r->handle;
|
||||
int msg_fd = hd->msg_fd;
|
||||
int port = hd->config.ctrl_port;
|
||||
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
ra->sd->for_async_req = false;
|
||||
free(ra->scratch);
|
||||
ra->scratch = NULL;
|
||||
ra->scratch_cur_size = 0;
|
||||
ra->scratch_size_limit = 0;
|
||||
free(ra->resp_hdrs);
|
||||
free(r->aux);
|
||||
free(r);
|
||||
|
||||
// Send a dummy control message(httpd_ctrl_data) to unblock the main HTTP server task from the select() call.
|
||||
// Since the current connection FD was marked as inactive for async requests, the main task
|
||||
// will now re-add this FD to its select() descriptor list. This ensures that subsequent requests
|
||||
// on the same FD are processed correctly
|
||||
struct httpd_ctrl_data msg = {.hc_msg = HTTPD_CTRL_MAX};
|
||||
|
||||
/* Reserve an mbox slot so we don't overrun ctrl_sock_semaphore's
|
||||
* accounting and starve concurrent httpd_queue_work() producers. The
|
||||
* httpd main task is the consumer and will drain the mbox shortly. */
|
||||
if (xSemaphoreTake(hd->ctrl_sock_semaphore, portMAX_DELAY) != pdTRUE) {
|
||||
ESP_LOGW(TAG, LOG_FMT("failed to acquire ctrl socket semaphore"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
int ret = cs_send_to_ctrl_sock(msg_fd, port, &msg, sizeof(msg));
|
||||
if (ret < 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("failed to send socket notification"));
|
||||
xSemaphoreGive(hd->ctrl_sock_semaphore);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("socket notification sent"));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int httpd_req_to_sockfd(httpd_req_t *r)
|
||||
{
|
||||
if (r == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!httpd_valid_req(r)) {
|
||||
ESP_LOGW(TAG, LOG_FMT("invalid request"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
return ra->sd->fd;
|
||||
}
|
||||
|
||||
static int httpd_sock_err(const char *ctx, int sockfd)
|
||||
{
|
||||
int errval;
|
||||
ESP_LOGW(TAG, LOG_FMT("error in %s : %d"), ctx, errno);
|
||||
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
case EINTR:
|
||||
errval = HTTPD_SOCK_ERR_TIMEOUT;
|
||||
break;
|
||||
case EINVAL:
|
||||
case EBADF:
|
||||
case EFAULT:
|
||||
case ENOTSOCK:
|
||||
errval = HTTPD_SOCK_ERR_INVALID;
|
||||
break;
|
||||
default:
|
||||
errval = HTTPD_SOCK_ERR_FAIL;
|
||||
}
|
||||
return errval;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int ret = send(sockfd, buf, buf_len, flags);
|
||||
if (ret < 0) {
|
||||
return httpd_sock_err("send", sockfd);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int ret = recv(sockfd, buf, buf_len, flags);
|
||||
if (ret < 0) {
|
||||
return httpd_sock_err("recv", sockfd);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int httpd_socket_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags)
|
||||
{
|
||||
struct sock_db *sess = httpd_sess_get(hd, sockfd);
|
||||
if (!sess) {
|
||||
return HTTPD_SOCK_ERR_INVALID;
|
||||
}
|
||||
if (!sess->send_fn) {
|
||||
return HTTPD_SOCK_ERR_INVALID;
|
||||
}
|
||||
return sess->send_fn(hd, sockfd, buf, buf_len, flags);
|
||||
}
|
||||
|
||||
int httpd_socket_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags)
|
||||
{
|
||||
struct sock_db *sess = httpd_sess_get(hd, sockfd);
|
||||
if (!sess) {
|
||||
return HTTPD_SOCK_ERR_INVALID;
|
||||
}
|
||||
if (!sess->recv_fn) {
|
||||
return HTTPD_SOCK_ERR_INVALID;
|
||||
}
|
||||
return sess->recv_fn(hd, sockfd, buf, buf_len, flags);
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
#include <errno.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <http_parser.h>
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include "esp_httpd_priv.h"
|
||||
|
||||
static const char *TAG = "httpd_uri";
|
||||
|
||||
static bool httpd_uri_match_simple(const char *uri1, const char *uri2, size_t len2)
|
||||
{
|
||||
return strlen(uri1) == len2 && // First match lengths
|
||||
(strncmp(uri1, uri2, len2) == 0); // Then match actual URIs
|
||||
}
|
||||
|
||||
bool httpd_uri_match_wildcard(const char *template, const char *uri, size_t len)
|
||||
{
|
||||
const size_t tpl_len = strlen(template);
|
||||
size_t exact_match_chars = tpl_len;
|
||||
|
||||
/* Check for trailing question mark and asterisk */
|
||||
const char last = (const char) (tpl_len > 0 ? template[tpl_len - 1] : 0);
|
||||
const char prevlast = (const char) (tpl_len > 1 ? template[tpl_len - 2] : 0);
|
||||
const bool asterisk = last == '*' || (prevlast == '*' && last == '?');
|
||||
const bool quest = last == '?' || (prevlast == '?' && last == '*');
|
||||
|
||||
/* Minimum template string length must be:
|
||||
* 0 : if neither of '*' and '?' are present
|
||||
* 1 : if only '*' is present
|
||||
* 2 : if only '?' is present
|
||||
* 3 : if both are present
|
||||
*
|
||||
* The expression (asterisk + quest*2) serves as a
|
||||
* case wise generator of these length values
|
||||
*/
|
||||
|
||||
/* abort in cases such as "?" with no preceding character (invalid template) */
|
||||
if (exact_match_chars < asterisk + quest*2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* account for special characters and the optional character if "?" is used */
|
||||
exact_match_chars -= asterisk + quest*2;
|
||||
|
||||
if (len < exact_match_chars) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!quest) {
|
||||
if (!asterisk && len != exact_match_chars) {
|
||||
/* no special characters and different length - strncmp would return false */
|
||||
return false;
|
||||
}
|
||||
/* asterisk allows arbitrary trailing characters, we ignore these using
|
||||
* exact_match_chars as the length limit */
|
||||
return (strncmp(template, uri, exact_match_chars) == 0);
|
||||
} else {
|
||||
/* question mark present */
|
||||
if (len > exact_match_chars && template[exact_match_chars] != uri[exact_match_chars]) {
|
||||
/* the optional character is present, but different */
|
||||
return false;
|
||||
}
|
||||
if (strncmp(template, uri, exact_match_chars) != 0) {
|
||||
/* the mandatory part differs */
|
||||
return false;
|
||||
}
|
||||
/* Now we know the URI is longer than the required part of template,
|
||||
* the mandatory part matches, and if the optional character is present, it is correct.
|
||||
* Match is OK if we have asterisk, i.e. any trailing characters are OK, or if
|
||||
* there are no characters beyond the optional character. */
|
||||
return asterisk || len <= exact_match_chars + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find handler with matching URI and method, and set
|
||||
* appropriate error code if URI or method not found */
|
||||
static httpd_uri_t* httpd_find_uri_handler(struct httpd_data *hd,
|
||||
const char *uri, size_t uri_len,
|
||||
httpd_method_t method,
|
||||
httpd_err_code_t *err)
|
||||
{
|
||||
if (err) {
|
||||
*err = HTTPD_404_NOT_FOUND;
|
||||
}
|
||||
|
||||
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
|
||||
if (!hd->hd_calls[i]) {
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri);
|
||||
|
||||
/* Check if custom URI matching function is set,
|
||||
* else use simple string compare */
|
||||
if (hd->config.uri_match_fn ?
|
||||
hd->config.uri_match_fn(hd->hd_calls[i]->uri, uri, uri_len) :
|
||||
httpd_uri_match_simple(hd->hd_calls[i]->uri, uri, uri_len)) {
|
||||
/* URIs match. Now check if method is supported */
|
||||
if (hd->hd_calls[i]->method == method || hd->hd_calls[i]->method == HTTP_ANY) {
|
||||
/* Match found! */
|
||||
if (err) {
|
||||
/* Unset any error that may
|
||||
* have been set earlier */
|
||||
*err = 0;
|
||||
}
|
||||
return hd->hd_calls[i];
|
||||
}
|
||||
/* URI found but method not allowed.
|
||||
* If URI is found later then this
|
||||
* error must be set to 0 */
|
||||
if (err) {
|
||||
*err = HTTPD_405_METHOD_NOT_ALLOWED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
|
||||
const httpd_uri_t *uri_handler)
|
||||
{
|
||||
if (handle == NULL || uri_handler == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
|
||||
/* Make sure another handler with matching URI and method
|
||||
* is not already registered. This will also catch cases
|
||||
* when a registered URI wildcard pattern already accounts
|
||||
* for the new URI being registered */
|
||||
if (httpd_find_uri_handler(handle, uri_handler->uri,
|
||||
strlen(uri_handler->uri),
|
||||
uri_handler->method, NULL) != NULL) {
|
||||
ESP_LOGW(TAG, LOG_FMT("handler %s with method %d already registered"),
|
||||
uri_handler->uri, uri_handler->method);
|
||||
return ESP_ERR_HTTPD_HANDLER_EXISTS;
|
||||
}
|
||||
|
||||
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
|
||||
if (hd->hd_calls[i] == NULL) {
|
||||
ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-malloc-leak") // False-positive detection. TODO GCC-366
|
||||
hd->hd_calls[i] = malloc(sizeof(httpd_uri_t));
|
||||
if (hd->hd_calls[i] == NULL) {
|
||||
/* Failed to allocate memory */
|
||||
return ESP_ERR_HTTPD_ALLOC_MEM;
|
||||
}
|
||||
ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-malloc-leak")
|
||||
|
||||
/* Copy URI string */
|
||||
hd->hd_calls[i]->uri = strdup(uri_handler->uri);
|
||||
if (hd->hd_calls[i]->uri == NULL) {
|
||||
/* Failed to allocate memory */
|
||||
free(hd->hd_calls[i]);
|
||||
hd->hd_calls[i] = NULL;
|
||||
return ESP_ERR_HTTPD_ALLOC_MEM;
|
||||
}
|
||||
|
||||
/* Copy remaining members */
|
||||
hd->hd_calls[i]->method = uri_handler->method;
|
||||
hd->hd_calls[i]->handler = uri_handler->handler;
|
||||
hd->hd_calls[i]->user_ctx = uri_handler->user_ctx;
|
||||
#ifdef CONFIG_HTTPD_WS_PRE_HANDSHAKE_CB_SUPPORT
|
||||
hd->hd_calls[i]->ws_pre_handshake_cb = uri_handler->ws_pre_handshake_cb;
|
||||
#endif
|
||||
#ifdef CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT
|
||||
hd->hd_calls[i]->ws_post_handshake_cb = uri_handler->ws_post_handshake_cb;
|
||||
#endif
|
||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||
hd->hd_calls[i]->is_websocket = uri_handler->is_websocket;
|
||||
hd->hd_calls[i]->handle_ws_control_frames = uri_handler->handle_ws_control_frames;
|
||||
if (uri_handler->supported_subprotocol) {
|
||||
hd->hd_calls[i]->supported_subprotocol = strdup(uri_handler->supported_subprotocol);
|
||||
if (hd->hd_calls[i]->supported_subprotocol == NULL) {
|
||||
/* Failed to allocate memory */
|
||||
free((void *)hd->hd_calls[i]->uri);
|
||||
free(hd->hd_calls[i]);
|
||||
hd->hd_calls[i] = NULL;
|
||||
return ESP_ERR_HTTPD_ALLOC_MEM;
|
||||
}
|
||||
} else {
|
||||
hd->hd_calls[i]->supported_subprotocol = NULL;
|
||||
}
|
||||
#endif
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] installed %s"), i, uri_handler->uri);
|
||||
return ESP_OK;
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] exists %s"), i, hd->hd_calls[i]->uri);
|
||||
}
|
||||
ESP_LOGW(TAG, LOG_FMT("no slots left for registering handler"));
|
||||
return ESP_ERR_HTTPD_HANDLERS_FULL;
|
||||
}
|
||||
|
||||
esp_err_t httpd_unregister_uri_handler(httpd_handle_t handle,
|
||||
const char *uri, httpd_method_t method)
|
||||
{
|
||||
if (handle == NULL || uri == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
|
||||
if (!hd->hd_calls[i]) {
|
||||
break;
|
||||
}
|
||||
if ((hd->hd_calls[i]->method == method) && // First match methods
|
||||
(strcmp(hd->hd_calls[i]->uri, uri) == 0)) { // Then match URI string
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
|
||||
|
||||
free((char*)hd->hd_calls[i]->uri);
|
||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||
free((char*)hd->hd_calls[i]->supported_subprotocol);
|
||||
#endif // CONFIG_HTTPD_WS_SUPPORT
|
||||
free(hd->hd_calls[i]);
|
||||
hd->hd_calls[i] = NULL;
|
||||
|
||||
/* Shift the remaining non null handlers in the array
|
||||
* forward by 1 so that order of insertion is maintained */
|
||||
for (i += 1; i < hd->config.max_uri_handlers; i++) {
|
||||
if (!hd->hd_calls[i]) {
|
||||
break;
|
||||
}
|
||||
hd->hd_calls[i-1] = hd->hd_calls[i];
|
||||
}
|
||||
/* Nullify the following non null entry */
|
||||
hd->hd_calls[i-1] = NULL;
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, LOG_FMT("handler %s with method %d not found"), uri, method);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char *uri)
|
||||
{
|
||||
if (handle == NULL || uri == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
bool found = false;
|
||||
|
||||
int i = 0, j = 0; // For keeping count of removed entries
|
||||
for (; i < hd->config.max_uri_handlers; i++) {
|
||||
if (!hd->hd_calls[i]) {
|
||||
break;
|
||||
}
|
||||
if (strcmp(hd->hd_calls[i]->uri, uri) == 0) { // Match URI strings
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, uri);
|
||||
|
||||
free((char*)hd->hd_calls[i]->uri);
|
||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||
free((char*)hd->hd_calls[i]->supported_subprotocol);
|
||||
#endif // CONFIG_HTTPD_WS_SUPPORT
|
||||
free(hd->hd_calls[i]);
|
||||
hd->hd_calls[i] = NULL;
|
||||
found = true;
|
||||
|
||||
j++; // Update count of removed entries
|
||||
} else {
|
||||
/* Shift the remaining non null handlers in the array
|
||||
* forward by j so that order of insertion is maintained */
|
||||
hd->hd_calls[i-j] = hd->hd_calls[i];
|
||||
}
|
||||
}
|
||||
/* Nullify the following non null entries */
|
||||
for (int k = (i - j); k < i; k++) {
|
||||
hd->hd_calls[k] = NULL;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
ESP_LOGW(TAG, LOG_FMT("no handler found for URI %s"), uri);
|
||||
}
|
||||
return (found ? ESP_OK : ESP_ERR_NOT_FOUND);
|
||||
}
|
||||
|
||||
void httpd_unregister_all_uri_handlers(struct httpd_data *hd)
|
||||
{
|
||||
for (unsigned i = 0; i < hd->config.max_uri_handlers; i++) {
|
||||
if (!hd->hd_calls[i]) {
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
|
||||
|
||||
free((char*)hd->hd_calls[i]->uri);
|
||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||
free((char*)hd->hd_calls[i]->supported_subprotocol);
|
||||
#endif // CONFIG_HTTPD_WS_SUPPORT
|
||||
free(hd->hd_calls[i]);
|
||||
hd->hd_calls[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t httpd_uri(struct httpd_data *hd)
|
||||
{
|
||||
httpd_uri_t *uri = NULL;
|
||||
httpd_req_t *req = &hd->hd_req;
|
||||
struct http_parser_url *res = &hd->hd_req_aux.url_parse_res;
|
||||
|
||||
/* For conveying URI not found/method not allowed */
|
||||
httpd_err_code_t err = 0;
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("request for %s with type %d"), req->uri, req->method);
|
||||
|
||||
/* URL parser result contains offset and length of path string */
|
||||
if (res->field_set & (1 << UF_PATH)) {
|
||||
uri = httpd_find_uri_handler(hd, req->uri + res->field_data[UF_PATH].off,
|
||||
res->field_data[UF_PATH].len, req->method, &err);
|
||||
}
|
||||
|
||||
/* If URI with method not found, respond with error code */
|
||||
if (uri == NULL) {
|
||||
switch (err) {
|
||||
case HTTPD_404_NOT_FOUND:
|
||||
ESP_LOGW(TAG, LOG_FMT("URI '%s' not found"), req->uri);
|
||||
return httpd_req_handle_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_req_handle_err(req, HTTPD_405_METHOD_NOT_ALLOWED);
|
||||
default:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Attach user context data (passed during URI registration) into request */
|
||||
req->user_ctx = uri->user_ctx;
|
||||
|
||||
/* Final step for a WebSocket handshake verification */
|
||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||
struct httpd_req_aux *aux = req->aux;
|
||||
if (uri->is_websocket && aux->ws_handshake_detect && uri->method == HTTP_GET) {
|
||||
#ifdef CONFIG_HTTPD_WS_PRE_HANDSHAKE_CB_SUPPORT
|
||||
if (uri->ws_pre_handshake_cb && uri->ws_pre_handshake_cb(req) != ESP_OK) {
|
||||
ESP_LOGW(TAG, LOG_FMT("ws_pre_handshake_cb failed"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
#endif /* CONFIG_HTTPD_WS_PRE_HANDSHAKE_CB_SUPPORT */
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("Responding WS handshake to sock %d"), aux->sd->fd);
|
||||
esp_err_t ret = httpd_ws_respond_server_handshake(&hd->hd_req, uri->supported_subprotocol);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
aux->sd->ws_handshake_done = true;
|
||||
aux->sd->ws_handler = uri->handler;
|
||||
aux->sd->ws_control_frames = uri->handle_ws_control_frames;
|
||||
aux->sd->ws_user_ctx = uri->user_ctx;
|
||||
|
||||
#ifdef CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT
|
||||
if (uri->ws_post_handshake_cb && uri->ws_post_handshake_cb(req) != ESP_OK) {
|
||||
ESP_LOGW(TAG, LOG_FMT("ws_post_handshake_cb failed"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
#endif /* CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT */
|
||||
/* If the request is websocket handshake, then do not call the uri->handler */
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif /* CONFIG_HTTPD_WS_SUPPORT */
|
||||
/* Invoke handler */
|
||||
if (uri->handler(req) != ESP_OK) {
|
||||
/* Handler returns error, this socket should be closed */
|
||||
ESP_LOGW(TAG, LOG_FMT("uri handler execution failed"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,741 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/random.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <psa/crypto.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/error.h>
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include "esp_httpd_priv.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||
|
||||
#define WS_SEND_OK (1 << 0)
|
||||
#define WS_SEND_FAILED (1 << 1)
|
||||
|
||||
#define SEC_WEBSOCKET_VERSION_HDR_MAX_LEN 255
|
||||
#define SEC_WEBSOCKET_KEY_HDR_MAX_LEN 255
|
||||
#define SEC_WEBSOCKET_VERSION "13"
|
||||
|
||||
typedef struct {
|
||||
httpd_ws_frame_t frame;
|
||||
httpd_handle_t handle;
|
||||
int socket;
|
||||
transfer_complete_cb callback;
|
||||
void *arg;
|
||||
bool blocking;
|
||||
EventGroupHandle_t transfer_done;
|
||||
} async_transfer_t;
|
||||
|
||||
static const char *TAG="httpd_ws";
|
||||
|
||||
/*
|
||||
* Bit masks for WebSocket frames.
|
||||
* Please refer to RFC6455 Section 5.2 for more details.
|
||||
*/
|
||||
#define HTTPD_WS_CONTINUE 0x00U
|
||||
#define HTTPD_WS_FIN_BIT 0x80U
|
||||
#define HTTPD_WS_OPCODE_BITS 0x0fU
|
||||
#define HTTPD_WS_MASK_BIT 0x80U
|
||||
#define HTTPD_WS_LENGTH_BITS 0x7fU
|
||||
|
||||
/*
|
||||
* The magic GUID string used for handshake
|
||||
* Please refer to RFC6455 Section 1.3 for more details.
|
||||
*/
|
||||
static const char ws_magic_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
/* Checks if any subprotocols from the comma separated list matches the supported one
|
||||
*
|
||||
* Returns true if the response should contain a protocol field
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Checks if any subprotocols from the comma separated list matches the supported one
|
||||
*
|
||||
* @param supported_subprotocol[in] The subprotocol supported by the URI
|
||||
* @param subprotocol[in], [in]: A comma separate list of subprotocols requested
|
||||
* @param buf_len Length of the buffer
|
||||
* @return true: found a matching subprotocol
|
||||
* @return false
|
||||
*/
|
||||
static bool httpd_ws_get_response_subprotocol(const char *supported_subprotocol, char *subprotocol, size_t buf_len)
|
||||
{
|
||||
/* Request didn't contain any subprotocols */
|
||||
if (strnlen(subprotocol, buf_len) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (supported_subprotocol == NULL) {
|
||||
ESP_LOGW(TAG, "Sec-WebSocket-Protocol %s not supported, URI do not support any subprotocols", subprotocol);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Get first subprotocol from comma separated list */
|
||||
char *rest = NULL;
|
||||
char *s = strtok_r(subprotocol, ", ", &rest);
|
||||
int supported_subprotocol_len = strlen(supported_subprotocol);
|
||||
while (s != NULL) {
|
||||
if (strlen(s) == supported_subprotocol_len &&
|
||||
strncmp(s, supported_subprotocol, supported_subprotocol_len) == 0) {
|
||||
ESP_LOGD(TAG, "Requested subprotocol supported: %s", s);
|
||||
return true;
|
||||
}
|
||||
s = strtok_r(NULL, ", ", &rest);
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Sec-WebSocket-Protocol %s not supported, supported subprotocol is %s", subprotocol, supported_subprotocol);
|
||||
|
||||
/* No matches */
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/* Send an HTTP error response for a rejected WS handshake.
|
||||
* Optionally adds a Sec-WebSocket-Version header (RFC 6455 §4.4).
|
||||
* Returns ESP_FAIL so callers can write: return httpd_ws_send_handshake_error(...).
|
||||
*/
|
||||
static esp_err_t httpd_ws_send_handshake_error(httpd_req_t *req, const char *status,
|
||||
const char *message, const char *supported_version)
|
||||
{
|
||||
if (supported_version != NULL) {
|
||||
if (httpd_resp_set_hdr(req, "Sec-WebSocket-Version", supported_version) != ESP_OK) {
|
||||
ESP_LOGE(TAG, LOG_FMT("Failed to set Sec-WebSocket-Version header"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
esp_err_t ret = httpd_resp_send_custom_err(req, status, message);
|
||||
return (ret == ESP_OK) ? ESP_FAIL : ret;
|
||||
}
|
||||
|
||||
esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req, const char *supported_subprotocol)
|
||||
{
|
||||
/* Probe if input parameters are valid or not */
|
||||
if (!req || !req->aux) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Argument is invalid"));
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Detect handshake - reject if handshake was ALREADY performed */
|
||||
struct httpd_req_aux *req_aux = req->aux;
|
||||
if (req_aux->sd->ws_handshake_done) {
|
||||
ESP_LOGW(TAG, LOG_FMT("State is invalid - Handshake has been performed"));
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
#if CONFIG_HTTPD_WS_STRICTER_RFC6455
|
||||
/* RFC 6455 §4.2.1: Host header MUST be present */
|
||||
size_t host_hdr_len = httpd_req_get_hdr_value_len(req, "Host");
|
||||
if (host_hdr_len == 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("\"Host\" is not found"));
|
||||
return httpd_ws_send_handshake_error(req, "400 Bad Request", "Missing Host header", NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* RFC 6455 §4.2.1: Sec-WebSocket-Version must be present and equal "13" */
|
||||
size_t version_hdr_len = httpd_req_get_hdr_value_len(req, "Sec-WebSocket-Version");
|
||||
if (version_hdr_len == 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is not found"));
|
||||
return httpd_ws_send_handshake_error(req, "400 Bad Request",
|
||||
"Missing Sec-WebSocket-Version header", NULL);
|
||||
}
|
||||
#if CONFIG_HTTPD_WS_STRICTER_RFC6455
|
||||
if (version_hdr_len > SEC_WEBSOCKET_VERSION_HDR_MAX_LEN) {
|
||||
ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is too long"));
|
||||
return httpd_ws_send_handshake_error(req, "400 Bad Request",
|
||||
"Invalid Sec-WebSocket-Version header", NULL);
|
||||
}
|
||||
#endif
|
||||
char *version_val = calloc(1, version_hdr_len + 1);
|
||||
if (version_val == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to allocate version header buffer");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Version", version_val, version_hdr_len + 1) != ESP_OK) {
|
||||
free(version_val);
|
||||
return httpd_ws_send_handshake_error(req, "400 Bad Request",
|
||||
"Invalid Sec-WebSocket-Version header", NULL);
|
||||
}
|
||||
/* WS version must be 13. Please refer to RFC6455 Section 4.1, Page 18 for more details. */
|
||||
if (strcasecmp(version_val, SEC_WEBSOCKET_VERSION) != 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is not \"13\", it is: %s"), version_val);
|
||||
free(version_val);
|
||||
return httpd_ws_send_handshake_error(req, "426 Upgrade Required",
|
||||
"WebSocket version not supported", "13");
|
||||
}
|
||||
free(version_val);
|
||||
|
||||
/* RFC 6455 §4.2.1 / §4.3: Sec-WebSocket-Key must be present (strict mode
|
||||
* additionally validates that it base64-decodes to a 16-byte nonce). */
|
||||
size_t sec_key_hdr_len = httpd_req_get_hdr_value_len(req, "Sec-WebSocket-Key");
|
||||
if (sec_key_hdr_len == 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Cannot find client key"));
|
||||
return httpd_ws_send_handshake_error(req, "400 Bad Request",
|
||||
"Missing Sec-WebSocket-Key header", NULL);
|
||||
}
|
||||
#if CONFIG_HTTPD_WS_STRICTER_RFC6455
|
||||
if (sec_key_hdr_len > SEC_WEBSOCKET_KEY_HDR_MAX_LEN) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Sec-WebSocket-Key header is too long"));
|
||||
return httpd_ws_send_handshake_error(req, "400 Bad Request",
|
||||
"Invalid Sec-WebSocket-Key header", NULL);
|
||||
}
|
||||
#endif
|
||||
char *sec_key_encoded = calloc(1, sec_key_hdr_len + 1);
|
||||
if (sec_key_encoded == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to allocate Sec-WebSocket-Key buffer");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Key", sec_key_encoded, sec_key_hdr_len + 1) != ESP_OK) {
|
||||
free(sec_key_encoded);
|
||||
return httpd_ws_send_handshake_error(req, "400 Bad Request",
|
||||
"Invalid Sec-WebSocket-Key header", NULL);
|
||||
}
|
||||
#if CONFIG_HTTPD_WS_STRICTER_RFC6455
|
||||
uint8_t decoded_key[16] = { 0 };
|
||||
size_t decoded_key_len = 0;
|
||||
if (mbedtls_base64_decode(decoded_key, sizeof(decoded_key), &decoded_key_len,
|
||||
(const unsigned char *)sec_key_encoded, strlen(sec_key_encoded)) != 0 ||
|
||||
decoded_key_len != sizeof(decoded_key)) {
|
||||
free(sec_key_encoded);
|
||||
ESP_LOGW(TAG, LOG_FMT("Sec-WebSocket-Key is not a valid base64-encoded 16-byte nonce"));
|
||||
return httpd_ws_send_handshake_error(req, "400 Bad Request",
|
||||
"Invalid Sec-WebSocket-Key header", NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Prepare server key (Sec-WebSocket-Accept), concat the string */
|
||||
char server_key_encoded[33] = { '\0' };
|
||||
uint8_t server_key_hash[20] = { 0 };
|
||||
size_t server_raw_text_len = strlen(sec_key_encoded) + strlen(ws_magic_uuid) + 1;
|
||||
char *server_raw_text = calloc(1, server_raw_text_len);
|
||||
if (server_raw_text == NULL) {
|
||||
free(sec_key_encoded);
|
||||
ESP_LOGE(TAG, "Failed to allocate handshake hash input buffer");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
strcpy(server_raw_text, sec_key_encoded);
|
||||
strcat(server_raw_text, ws_magic_uuid);
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("Server key before encoding: %s"), server_raw_text);
|
||||
|
||||
/* Generate SHA-1 hash */
|
||||
psa_hash_operation_t sha1_operation = PSA_HASH_OPERATION_INIT;
|
||||
psa_status_t status = psa_hash_setup(&sha1_operation, PSA_ALG_SHA_1);
|
||||
if (status != PSA_SUCCESS) {
|
||||
free(server_raw_text);
|
||||
free(sec_key_encoded);
|
||||
ESP_LOGE(TAG, "Failed to setup SHA-1 operation");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
status = psa_hash_update(&sha1_operation, (uint8_t *)server_raw_text, strlen(server_raw_text));
|
||||
if (status != PSA_SUCCESS) {
|
||||
free(server_raw_text);
|
||||
free(sec_key_encoded);
|
||||
ESP_LOGE(TAG, "Failed to update SHA-1 hash");
|
||||
psa_hash_abort(&sha1_operation);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
size_t hash_length;
|
||||
status = psa_hash_finish(&sha1_operation, server_key_hash, sizeof(server_key_hash), &hash_length);
|
||||
free(server_raw_text);
|
||||
if (status != PSA_SUCCESS || hash_length != sizeof(server_key_hash)) {
|
||||
free(sec_key_encoded);
|
||||
ESP_LOGE(TAG, "Failed to finish SHA-1 hash");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Encode to Base64 */
|
||||
size_t encoded_len = 0;
|
||||
mbedtls_base64_encode((uint8_t *)server_key_encoded, sizeof(server_key_encoded), &encoded_len,
|
||||
server_key_hash, sizeof(server_key_hash));
|
||||
|
||||
free(sec_key_encoded);
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("Generated server key: %s"), server_key_encoded);
|
||||
|
||||
char subprotocol[50] = { '\0' };
|
||||
if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Protocol", subprotocol, sizeof(subprotocol) - 1) == ESP_ERR_HTTPD_RESULT_TRUNC) {
|
||||
ESP_LOGW(TAG, "Sec-WebSocket-Protocol length exceeded buffer size of %"NEWLIB_NANO_COMPAT_FORMAT", was truncated", NEWLIB_NANO_COMPAT_CAST(sizeof(subprotocol)));
|
||||
}
|
||||
|
||||
|
||||
/* Prepare the Switching Protocol response */
|
||||
char tx_buf[192] = { '\0' };
|
||||
int fmt_len = snprintf(tx_buf, sizeof(tx_buf),
|
||||
"HTTP/1.1 101 Switching Protocols\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Accept: %s\r\n", server_key_encoded);
|
||||
|
||||
if (fmt_len < 0 || fmt_len > sizeof(tx_buf)) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Failed to prepare Tx buffer"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if ( httpd_ws_get_response_subprotocol(supported_subprotocol, subprotocol, sizeof(subprotocol))) {
|
||||
ESP_LOGD(TAG, "subprotocol: %s", subprotocol);
|
||||
int r = snprintf(tx_buf + fmt_len, sizeof(tx_buf) - fmt_len, "Sec-WebSocket-Protocol: %s\r\n", supported_subprotocol);
|
||||
if (r <= 0) {
|
||||
ESP_LOGE(TAG, "Error in response generation"
|
||||
"(snprintf of subprotocol returned %d, buffer size: %"NEWLIB_NANO_COMPAT_FORMAT, r, NEWLIB_NANO_COMPAT_CAST(sizeof(tx_buf)));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
fmt_len += r;
|
||||
|
||||
if (fmt_len >= sizeof(tx_buf)) {
|
||||
ESP_LOGE(TAG, "Error in response generation"
|
||||
"(snprintf of subprotocol returned %d, desired response len: %d, buffer size: %"NEWLIB_NANO_COMPAT_FORMAT, r, fmt_len, NEWLIB_NANO_COMPAT_CAST(sizeof(tx_buf)));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
int r = snprintf(tx_buf + fmt_len, sizeof(tx_buf) - fmt_len, "\r\n");
|
||||
if (r <= 0) {
|
||||
ESP_LOGE(TAG, "Error in response generation"
|
||||
"(snprintf of subprotocol returned %d, buffer size: %"NEWLIB_NANO_COMPAT_FORMAT, r, NEWLIB_NANO_COMPAT_CAST(sizeof(tx_buf)));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
fmt_len += r;
|
||||
if (fmt_len >= sizeof(tx_buf)) {
|
||||
ESP_LOGE(TAG, "Error in response generation"
|
||||
"(snprintf of header terminal returned %d, desired response len: %d, buffer size: %"NEWLIB_NANO_COMPAT_FORMAT, r, fmt_len, NEWLIB_NANO_COMPAT_CAST(sizeof(tx_buf)));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Send off the response */
|
||||
if (httpd_send(req, tx_buf, fmt_len) < 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Failed to send the response"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t httpd_ws_check_req(httpd_req_t *req)
|
||||
{
|
||||
/* Probe if input parameters are valid or not */
|
||||
if (!req || !req->aux) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Argument is null"));
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Detect handshake - reject if handshake was NOT YET performed */
|
||||
struct httpd_req_aux *req_aux = req->aux;
|
||||
if (!req_aux->sd->ws_handshake_done) {
|
||||
ESP_LOGW(TAG, LOG_FMT("State is invalid - No handshake performed"));
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t httpd_ws_unmask_payload(uint8_t *payload, size_t len, const uint8_t *mask_key, size_t mask_offset)
|
||||
{
|
||||
if (len < 1 || !payload) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Invalid payload provided"));
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
for (size_t idx = 0; idx < len; idx++) {
|
||||
payload[idx] = (payload[idx] ^ mask_key[(idx + mask_offset) % 4]);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t httpd_ws_recv_frame_internal(httpd_req_t *req, httpd_ws_frame_t *frame, size_t max_len, bool partial)
|
||||
{
|
||||
esp_err_t ret = httpd_ws_check_req(req);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct httpd_req_aux *aux = req->aux;
|
||||
if (aux == NULL) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Invalid Aux pointer"));
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!frame) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Frame pointer is invalid"));
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
/* If frame len is 0, will get frame len from req. Otherwise regard frame len already achieved by calling httpd_ws_recv_frame before */
|
||||
if (frame->len == 0) {
|
||||
/* Assign the frame info from the previous reading */
|
||||
frame->type = aux->ws_type;
|
||||
frame->final = aux->ws_final;
|
||||
|
||||
/* Grab the second byte */
|
||||
uint8_t second_byte = 0;
|
||||
int recv_ret = httpd_recv_with_opt(req, (char *)&second_byte, sizeof(second_byte), HTTPD_RECV_OPT_BLOCKING);
|
||||
if (recv_ret != (int)sizeof(second_byte)) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Failed to receive the second byte"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Parse the second byte */
|
||||
/* Please refer to RFC6455 Section 5.2 for more details */
|
||||
bool masked = (second_byte & HTTPD_WS_MASK_BIT) != 0;
|
||||
|
||||
/* Interpret length */
|
||||
uint8_t init_len = second_byte & HTTPD_WS_LENGTH_BITS;
|
||||
if (init_len < 126) {
|
||||
/* Case 1: If length is 0-125, then this length bit is 7 bits */
|
||||
frame->len = init_len;
|
||||
} else if (init_len == 126) {
|
||||
/* Case 2: If length byte is 126, then this frame's length bit is 16 bits */
|
||||
uint8_t length_bytes[2] = { 0 };
|
||||
recv_ret = httpd_recv_with_opt(req, (char *)length_bytes, sizeof(length_bytes), HTTPD_RECV_OPT_BLOCKING);
|
||||
if (recv_ret != (int)sizeof(length_bytes)) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Failed to receive 2 bytes length"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
frame->len = ((uint32_t)(length_bytes[0] << 8U) | (length_bytes[1]));
|
||||
} else if (init_len == 127) {
|
||||
/* Case 3: If length is byte 127, then this frame's length bit is 64 bits */
|
||||
uint8_t length_bytes[8] = { 0 };
|
||||
recv_ret = httpd_recv_with_opt(req, (char *)length_bytes, sizeof(length_bytes), HTTPD_RECV_OPT_BLOCKING);
|
||||
if (recv_ret != (int)sizeof(length_bytes)) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Failed to receive 8 bytes length"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
frame->len = (((uint64_t)length_bytes[0] << 56U) |
|
||||
((uint64_t)length_bytes[1] << 48U) |
|
||||
((uint64_t)length_bytes[2] << 40U) |
|
||||
((uint64_t)length_bytes[3] << 32U) |
|
||||
((uint64_t)length_bytes[4] << 24U) |
|
||||
((uint64_t)length_bytes[5] << 16U) |
|
||||
((uint64_t)length_bytes[6] << 8U) |
|
||||
((uint64_t)length_bytes[7]));
|
||||
}
|
||||
frame->left_len = frame->len;
|
||||
|
||||
/* If this frame is masked, dump the mask as well */
|
||||
if (masked) {
|
||||
recv_ret = httpd_recv_with_opt(req, (char *)aux->mask_key, sizeof(aux->mask_key), HTTPD_RECV_OPT_BLOCKING);
|
||||
if (recv_ret != (int)sizeof(aux->mask_key)) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Failed to receive mask key"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
} else {
|
||||
/* If the WS frame from client to server is not masked, it should be rejected.
|
||||
* Please refer to RFC6455 Section 5.2 for more details. */
|
||||
ESP_LOGW(TAG, LOG_FMT("WS frame is not properly masked."));
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
/* If max_len is 0, regard it OK for userspace to get frame len */
|
||||
if (max_len == 0) {
|
||||
ESP_LOGD(TAG, "regard max_len == 0 is OK for user to get frame len");
|
||||
return ESP_OK;
|
||||
}
|
||||
if (frame->len > max_len) {
|
||||
/* When reading entire packet at once, we only accept the incoming packet length that is smaller than the max_len (or it will overflow the buffer!) */
|
||||
if (!partial) {
|
||||
ESP_LOGW(TAG, LOG_FMT("WS Message too long"));
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("WS Message too long. User will have to call read again"));
|
||||
}
|
||||
|
||||
/* Receive buffer */
|
||||
/* If there's nothing to receive, return and stop here. */
|
||||
if (frame->left_len == 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (frame->payload == NULL) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Payload buffer is null"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
size_t left_len = (max_len < frame->left_len) ? max_len : frame->left_len;
|
||||
size_t offset = 0;
|
||||
|
||||
while (left_len > 0) {
|
||||
int read_len = httpd_recv_with_opt(req, (char *)frame->payload + offset, left_len, HTTPD_RECV_OPT_NONE);
|
||||
if (read_len <= 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Failed to receive payload"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
offset += read_len;
|
||||
left_len -= read_len;
|
||||
|
||||
ESP_LOGD(TAG, "Frame length: %"NEWLIB_NANO_COMPAT_FORMAT", Bytes Read: %"NEWLIB_NANO_COMPAT_FORMAT, NEWLIB_NANO_COMPAT_CAST(frame->len), NEWLIB_NANO_COMPAT_CAST(offset));
|
||||
}
|
||||
|
||||
size_t mask_offset = frame->len - frame->left_len;
|
||||
frame->left_len -= offset;
|
||||
|
||||
/* Unmask payload */
|
||||
httpd_ws_unmask_payload(frame->payload, offset, aux->mask_key, mask_offset);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t httpd_ws_recv_frame(httpd_req_t *req, httpd_ws_frame_t *frame, size_t max_len) {
|
||||
return httpd_ws_recv_frame_internal(req, frame, max_len, false);
|
||||
}
|
||||
|
||||
esp_err_t httpd_ws_recv_frame_part(httpd_req_t *req, httpd_ws_frame_t *frame, size_t max_len) {
|
||||
return httpd_ws_recv_frame_internal(req, frame, max_len, true);
|
||||
}
|
||||
|
||||
esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *frame)
|
||||
{
|
||||
esp_err_t ret = httpd_ws_check_req(req);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
return httpd_ws_send_frame_async(req->handle, httpd_req_to_sockfd(req), frame);
|
||||
}
|
||||
|
||||
esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Argument is invalid"));
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Prepare Tx buffer - maximum length is 14, which includes 2 bytes header, 8 bytes length, 4 bytes mask key */
|
||||
uint8_t tx_len = 0;
|
||||
uint8_t header_buf[10] = {0 };
|
||||
/* Set the `FIN` bit by default if message is not fragmented. Else, set it as per the `final` field */
|
||||
header_buf[0] |= (!frame->fragmented) ? HTTPD_WS_FIN_BIT : (frame->final? HTTPD_WS_FIN_BIT: HTTPD_WS_CONTINUE);
|
||||
header_buf[0] |= frame->type; /* Type (opcode): 4 bits */
|
||||
|
||||
if (frame->len <= 125) {
|
||||
header_buf[1] = frame->len & 0x7fU; /* Length for 7 bits */
|
||||
tx_len = 2;
|
||||
} else if (frame->len > 125 && frame->len < UINT16_MAX) {
|
||||
header_buf[1] = 126; /* Length for 16 bits */
|
||||
header_buf[2] = (frame->len >> 8U) & 0xffU;
|
||||
header_buf[3] = frame->len & 0xffU;
|
||||
tx_len = 4;
|
||||
} else {
|
||||
header_buf[1] = 127; /* Length for 64 bits */
|
||||
uint8_t shift_idx = sizeof(uint64_t) - 1; /* Shift index starts at 7 */
|
||||
uint64_t len64 = frame->len; /* Raise variable size to make sure we won't shift by more bits
|
||||
* than the length has (to avoid undefined behaviour) */
|
||||
for (int8_t idx = 2; idx <= 9; idx++) {
|
||||
/* Now do shifting (be careful of endianness, i.e. when buffer index is 2, frame length shift index is 7) */
|
||||
header_buf[idx] = (len64 >> (shift_idx * 8)) & 0xffU;
|
||||
shift_idx--;
|
||||
}
|
||||
tx_len = 10;
|
||||
}
|
||||
|
||||
/* WebSocket server does not required to mask response payload, so leave the MASK bit as 0. */
|
||||
header_buf[1] &= (~HTTPD_WS_MASK_BIT);
|
||||
|
||||
struct sock_db *sess = httpd_sess_get(hd, fd);
|
||||
if (!sess) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Send off header */
|
||||
if (sess->send_fn(hd, fd, (const char *)header_buf, tx_len, 0) < 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Failed to send WS header"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Send off payload */
|
||||
if(frame->len > 0 && frame->payload != NULL) {
|
||||
if (sess->send_fn(hd, fd, (const char *)frame->payload, frame->len, 0) < 0) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Failed to send WS payload"));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t httpd_ws_get_frame_type(httpd_req_t *req)
|
||||
{
|
||||
esp_err_t ret = httpd_ws_check_req(req);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct httpd_req_aux *aux = req->aux;
|
||||
if (aux == NULL) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Invalid Aux pointer"));
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct sock_db *sd = aux->sd;
|
||||
if (sd == NULL) {
|
||||
ESP_LOGW(TAG, LOG_FMT("Invalid sd pointer"));
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Read the first byte from the frame to get the FIN flag and Opcode */
|
||||
/* Please refer to RFC6455 Section 5.2 for more details */
|
||||
uint8_t first_byte = 0;
|
||||
int recv_ret = httpd_recv_with_opt(req, (char *)&first_byte, sizeof(first_byte), HTTPD_RECV_OPT_BLOCKING);
|
||||
if (recv_ret != (int)sizeof(first_byte)) {
|
||||
/* If we fail to read exactly one byte, this socket FD is invalid or the frame header is incomplete. */
|
||||
/* Here we mark it as a Close message and close it later. */
|
||||
ESP_LOGW(TAG, LOG_FMT("Failed to read header byte (socket FD invalid), closing socket now"));
|
||||
aux->ws_final = true;
|
||||
aux->ws_type = HTTPD_WS_TYPE_CLOSE;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("First byte received: 0x%02X"), first_byte);
|
||||
|
||||
/* Decode the FIN flag and Opcode from the byte */
|
||||
aux->ws_final = (first_byte & HTTPD_WS_FIN_BIT) != 0;
|
||||
aux->ws_type = (first_byte & HTTPD_WS_OPCODE_BITS);
|
||||
|
||||
/* If userspace requests control frames, do not deal with the control frames */
|
||||
if (!sd->ws_control_frames) {
|
||||
ESP_LOGD(TAG, LOG_FMT("Handler not requests control frames"));
|
||||
|
||||
/* Reply to PING. For PONG and CLOSE, it will be handled elsewhere. */
|
||||
if (aux->ws_type == HTTPD_WS_TYPE_PING) {
|
||||
ESP_LOGD(TAG, LOG_FMT("Got a WS PING frame, Replying PONG..."));
|
||||
|
||||
/* Read the rest of the PING frame, for PONG to reply back. */
|
||||
/* Please refer to RFC6455 Section 5.5.2 for more details */
|
||||
httpd_ws_frame_t frame;
|
||||
uint8_t frame_buf[128] = { 0 };
|
||||
memset(&frame, 0, sizeof(httpd_ws_frame_t));
|
||||
frame.payload = frame_buf;
|
||||
|
||||
if (httpd_ws_recv_frame(req, &frame, 126) != ESP_OK) {
|
||||
ESP_LOGD(TAG, LOG_FMT("Cannot receive the full PING frame"));
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Now turn the frame to PONG */
|
||||
frame.type = HTTPD_WS_TYPE_PONG;
|
||||
return httpd_ws_send_frame(req, &frame);
|
||||
} else if (aux->ws_type == HTTPD_WS_TYPE_CLOSE) {
|
||||
ESP_LOGD(TAG, LOG_FMT("Got a WS CLOSE frame, Replying CLOSE..."));
|
||||
|
||||
/* Read the rest of the CLOSE frame and response */
|
||||
/* Please refer to RFC6455 Section 5.5.1 for more details */
|
||||
httpd_ws_frame_t frame;
|
||||
uint8_t frame_buf[128] = { 0 };
|
||||
memset(&frame, 0, sizeof(httpd_ws_frame_t));
|
||||
frame.payload = frame_buf;
|
||||
|
||||
if (httpd_ws_recv_frame(req, &frame, 126) != ESP_OK) {
|
||||
ESP_LOGD(TAG, LOG_FMT("Cannot receive the full CLOSE frame"));
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
frame.len = 0;
|
||||
frame.type = HTTPD_WS_TYPE_CLOSE;
|
||||
frame.payload = NULL;
|
||||
return httpd_ws_send_frame(req, &frame);
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
httpd_ws_client_info_t httpd_ws_get_fd_info(httpd_handle_t hd, int fd)
|
||||
{
|
||||
struct sock_db *sess = httpd_sess_get(hd, fd);
|
||||
|
||||
if (sess == NULL) {
|
||||
return HTTPD_WS_CLIENT_INVALID;
|
||||
}
|
||||
bool is_active_ws = sess->ws_handshake_done && (!sess->ws_close);
|
||||
return is_active_ws ? HTTPD_WS_CLIENT_WEBSOCKET : HTTPD_WS_CLIENT_HTTP;
|
||||
}
|
||||
|
||||
static void httpd_ws_send_cb(void *arg)
|
||||
{
|
||||
async_transfer_t *trans = arg;
|
||||
|
||||
esp_err_t err = httpd_ws_send_frame_async(trans->handle, trans->socket, &trans->frame);
|
||||
|
||||
if (trans->blocking) {
|
||||
xEventGroupSetBits(trans->transfer_done, err ? WS_SEND_FAILED : WS_SEND_OK);
|
||||
} else if (trans->callback) {
|
||||
trans->callback(err, trans->socket, trans->arg);
|
||||
}
|
||||
|
||||
free(trans);
|
||||
}
|
||||
|
||||
esp_err_t httpd_ws_send_data(httpd_handle_t handle, int socket, httpd_ws_frame_t *frame)
|
||||
{
|
||||
async_transfer_t *transfer = calloc(1, sizeof(async_transfer_t));
|
||||
if (transfer == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
EventGroupHandle_t transfer_done = xEventGroupCreate();
|
||||
if (!transfer_done) {
|
||||
free(transfer);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
transfer->blocking = true;
|
||||
transfer->handle = handle;
|
||||
transfer->socket = socket;
|
||||
transfer->transfer_done = transfer_done;
|
||||
memcpy(&transfer->frame, frame, sizeof(httpd_ws_frame_t));
|
||||
|
||||
esp_err_t err = httpd_queue_work(handle, httpd_ws_send_cb, transfer);
|
||||
if (err != ESP_OK) {
|
||||
vEventGroupDelete(transfer_done);
|
||||
free(transfer);
|
||||
return err;
|
||||
}
|
||||
|
||||
EventBits_t status = xEventGroupWaitBits(transfer_done, WS_SEND_OK | WS_SEND_FAILED,
|
||||
pdTRUE, pdFALSE, portMAX_DELAY);
|
||||
|
||||
vEventGroupDelete(transfer_done);
|
||||
|
||||
return (status & WS_SEND_OK) ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t httpd_ws_send_data_async(httpd_handle_t handle, int socket, httpd_ws_frame_t *frame,
|
||||
transfer_complete_cb callback, void *arg)
|
||||
{
|
||||
async_transfer_t *transfer = calloc(1, sizeof(async_transfer_t));
|
||||
if (transfer == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
transfer->arg = arg;
|
||||
transfer->callback = callback;
|
||||
transfer->handle = handle;
|
||||
transfer->socket = socket;
|
||||
memcpy(&transfer->frame, frame, sizeof(httpd_ws_frame_t));
|
||||
|
||||
esp_err_t err = httpd_queue_work(handle, httpd_ws_send_cb, transfer);
|
||||
|
||||
if (err) {
|
||||
free(transfer);
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_HTTPD_WS_SUPPORT */
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _OSAL_H_
|
||||
#define _OSAL_H_
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define OS_SUCCESS ESP_OK
|
||||
#define OS_FAIL ESP_FAIL
|
||||
|
||||
typedef TaskHandle_t othread_t;
|
||||
|
||||
static inline int httpd_os_thread_create(othread_t *thread,
|
||||
const char *name, uint16_t stacksize, int prio,
|
||||
void (*thread_routine)(void *arg), void *arg,
|
||||
BaseType_t core_id, uint32_t caps)
|
||||
{
|
||||
int ret = xTaskCreatePinnedToCoreWithCaps(thread_routine, name, stacksize, arg, prio, thread, core_id, caps);
|
||||
if (ret == pdPASS) {
|
||||
return OS_SUCCESS;
|
||||
}
|
||||
return OS_FAIL;
|
||||
}
|
||||
|
||||
/* Only self delete is supported */
|
||||
static inline void httpd_os_thread_delete(void)
|
||||
{
|
||||
vTaskDeleteWithCaps(xTaskGetCurrentTaskHandle());
|
||||
}
|
||||
|
||||
static inline void httpd_os_thread_sleep(int msecs)
|
||||
{
|
||||
vTaskDelay(msecs / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
static inline othread_t httpd_os_thread_handle(void)
|
||||
{
|
||||
return xTaskGetCurrentTaskHandle();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ! _OSAL_H_ */
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "ctrl_sock.h"
|
||||
|
||||
#if CONFIG_IDF_TARGET_LINUX
|
||||
#define IPV4_ENABLED 1
|
||||
#define IPV6_ENABLED 1
|
||||
#define LOOPBACK_ENABLED 1
|
||||
#else // CONFIG_IDF_TARGET_LINUX
|
||||
#define IPV4_ENABLED CONFIG_LWIP_IPV4
|
||||
#define IPV6_ENABLED CONFIG_LWIP_IPV6
|
||||
#define LOOPBACK_ENABLED CONFIG_LWIP_NETIF_LOOPBACK
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
/* Control socket, because in some network stacks select can't be woken up any
|
||||
* other way
|
||||
*/
|
||||
int cs_create_ctrl_sock(int port)
|
||||
{
|
||||
#if !LOOPBACK_ENABLED
|
||||
ESP_LOGE("esp_http_server", "Please enable LWIP_NETIF_LOOPBACK for %s API", __func__);
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret;
|
||||
struct sockaddr_storage addr = {};
|
||||
socklen_t addr_len = 0;
|
||||
#if IPV4_ENABLED
|
||||
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
|
||||
addr4->sin_family = AF_INET;
|
||||
addr4->sin_port = htons(port);
|
||||
inet_aton("127.0.0.1", &addr4->sin_addr);
|
||||
addr_len = sizeof(struct sockaddr_in);
|
||||
#else
|
||||
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
|
||||
addr6->sin6_family = AF_INET6;
|
||||
addr6->sin6_port = htons(port);
|
||||
inet6_aton("::1", &addr6->sin6_addr);
|
||||
addr_len = sizeof(struct sockaddr_in6);
|
||||
#endif
|
||||
ret = bind(fd, (struct sockaddr *)&addr, addr_len);
|
||||
if (ret < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
void cs_free_ctrl_sock(int fd)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int cs_send_to_ctrl_sock(int send_fd, int port, void *data, unsigned int data_len)
|
||||
{
|
||||
int ret;
|
||||
struct sockaddr_storage to_addr = {};
|
||||
socklen_t addr_len = 0;
|
||||
#if IPV4_ENABLED
|
||||
struct sockaddr_in *addr4 = (struct sockaddr_in *)&to_addr;
|
||||
addr4->sin_family = AF_INET;
|
||||
addr4->sin_port = htons(port);
|
||||
inet_aton("127.0.0.1", &addr4->sin_addr);
|
||||
addr_len = sizeof(struct sockaddr_in);
|
||||
#else
|
||||
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&to_addr;
|
||||
addr6->sin6_family = AF_INET6;
|
||||
addr6->sin6_port = htons(port);
|
||||
inet6_aton("::1", &addr6->sin6_addr);
|
||||
addr_len = sizeof(struct sockaddr_in6);
|
||||
#endif
|
||||
ret = sendto(send_fd, data, data_len, 0, (struct sockaddr *)&to_addr, addr_len);
|
||||
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cs_recv_from_ctrl_sock(int fd, void *data, unsigned int data_len)
|
||||
{
|
||||
int ret;
|
||||
ret = recvfrom(fd, data, data_len, 0, NULL, NULL);
|
||||
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file ctrl_sock.h
|
||||
* \brief Control Socket for select() wakeup
|
||||
*
|
||||
* LWIP doesn't allow an easy mechanism to on-demand wakeup a thread
|
||||
* sleeping on select. This is a common requirement for sending
|
||||
* control commands to a network server. This control socket API
|
||||
* facilitates the same.
|
||||
*/
|
||||
#ifndef _CTRL_SOCK_H_
|
||||
#define _CTRL_SOCK_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Create a control socket
|
||||
*
|
||||
* LWIP doesn't allow an easy mechanism to on-demand wakeup a thread
|
||||
* sleeping on select. This is a common requirement for sending
|
||||
* control commands to a network server. This control socket API
|
||||
* facilitates the same.
|
||||
*
|
||||
* This API will create a UDP control socket on the specified port. It
|
||||
* will return a socket descriptor that can then be added to your
|
||||
* fd_set in select()
|
||||
*
|
||||
* @param[in] port the local port on which the control socket will listen
|
||||
*
|
||||
* @return - the socket descriptor that can be added to the fd_set in select.
|
||||
* - an error code if less than zero
|
||||
*/
|
||||
int cs_create_ctrl_sock(int port);
|
||||
|
||||
/**
|
||||
* @brief Free the control socket
|
||||
*
|
||||
* This frees up the control socket that was earlier created using
|
||||
* cs_create_ctrl_sock()
|
||||
*
|
||||
* @param[in] fd the socket descriptor associated with this control socket
|
||||
*/
|
||||
void cs_free_ctrl_sock(int fd);
|
||||
|
||||
/**
|
||||
* @brief Send data to control socket
|
||||
*
|
||||
* This API sends data to the control socket. If a server is blocked
|
||||
* on select() with the control socket, this call will wake up that
|
||||
* server.
|
||||
*
|
||||
* @param[in] send_fd the socket for sending ctrl messages
|
||||
* @param[in] port the port on which the control socket was created
|
||||
* @param[in] data pointer to a buffer that contains data to send on the socket
|
||||
* @param[in] data_len the length of the data contained in the buffer pointed to be data
|
||||
*
|
||||
* @return - the number of bytes sent to the control socket
|
||||
* - an error code if less than zero
|
||||
*/
|
||||
int cs_send_to_ctrl_sock(int send_fd, int port, void *data, unsigned int data_len);
|
||||
|
||||
/**
|
||||
* @brief Receive data from control socket
|
||||
*
|
||||
* This API receives any data that was sent to the control
|
||||
* socket. This will be typically called from the server thread to
|
||||
* process any commands on this socket.
|
||||
*
|
||||
* @param[in] fd the socket descriptor of the control socket
|
||||
* @param[in] data pointer to a buffer that will be used to store
|
||||
* received from the control socket
|
||||
* @param[in] data_len the maximum length of the data that can be
|
||||
* stored in the buffer pointed by data
|
||||
*
|
||||
* @return - the number of bytes received from the control socket
|
||||
* - an error code if less than zero
|
||||
*/
|
||||
int cs_recv_from_ctrl_sock(int fd, void *data, unsigned int data_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ! _CTRL_SOCK_H_ */
|
||||
Reference in New Issue
Block a user