feature/esp_http_server_idf_v3.2: Added the esp_http_server component from idf.

This commit is contained in:
Supreet Deshpande
2019-02-18 14:13:12 +05:30
parent 3a30f08a0b
commit 825a53199d
45 changed files with 7246 additions and 0 deletions

View File

@ -0,0 +1,531 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _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"
#ifdef __cplusplus
extern "C" {
#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. This should not
* exceed the scratch buffer size and should atleast be 8 bytes */
#define PARSER_BLOCK_SIZE 128
/* Calculate the maximum size needed for the scratch buffer */
#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN)
/* Formats a log string to prepend context function name */
#define LOG_FMT(x) "%s: " x, __func__
/**
* @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 Error codes sent by server in case of errors
* encountered during processing of an HTTP request
*/
typedef enum {
/* For any unexpected errors during parsing, like unexpected
* state transitions, or unhandled errors.
*/
HTTPD_500_SERVER_ERROR = 0,
/* For methods not supported by http_parser. Presently
* http_parser halts parsing when such methods are
* encountered and so the server responds with 400 Bad
* Request error instead.
*/
HTTPD_501_METHOD_NOT_IMPLEMENTED,
/* When HTTP version is not 1.1 */
HTTPD_505_VERSION_NOT_SUPPORTED,
/* Returned when http_parser halts parsing due to incorrect
* syntax of request, unsupported method in request URI or
* due to chunked encoding option present in headers
*/
HTTPD_400_BAD_REQUEST,
/* When requested URI is not found */
HTTPD_404_NOT_FOUND,
/* When URI found, but method has no handler registered */
HTTPD_405_METHOD_NOT_ALLOWED,
/* Intended for recv timeout. Presently it's being sent
* for other recv errors as well. Client should expect the
* server to immediatly close the connection after
* responding with this.
*/
HTTPD_408_REQ_TIMEOUT,
/* Intended for responding to chunked encoding, which is
* not supported currently. Though unhandled http_parser
* callback for chunked request returns "400 Bad Request"
*/
HTTPD_411_LENGTH_REQUIRED,
/* URI length greater than HTTPD_MAX_URI_LEN */
HTTPD_414_URI_TOO_LONG,
/* Headers section larger thn HTTPD_MAX_REQ_HDR_LEN */
HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE,
/* There is no particular HTTP error code for not supporting
* upgrade. For this respond with 200 OK. Client expects status
* code 101 if upgrade were supported, so 200 should be fine.
*/
HTTPD_XXX_UPGRADE_NOT_SUPPORTED
} httpd_err_resp_t;
/**
* @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 */
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 */
int64_t timestamp; /*!< Timestamp indicating when the socket was last used */
char pending_data[PARSER_BLOCK_SIZE]; /*!< Buffer for pending data to be received */
size_t pending_len; /*!< Length of pending data to be received */
};
/**
* @brief Auxilary 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[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */
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 */
};
/**
* @brief Server data for each instance. This is exposed publicaly 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 */
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 */
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 */
};
/******************* Group : Session Management ********************/
/** @name Session Management
* Functions related to HTTP session management
* @{
*/
/**
* @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 queueing 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] clifd Descriptor of the client from which data is to be received
*
* @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, int clifd);
/**
* @brief Remove client descriptor from the session / socket database
* and close the connection for this client.
*
* @note The returned descriptor should be used by httpd_sess_iterate()
* to continue the iteration correctly. This ensurs that the
* iteration is not restarted abruptly which may cause reading from
* a socket which has been already processed and thus blocking
* the server loop until data appears on that socket.
*
* @param[in] hd Server instance data
* @param[in] clifd Descriptor of the client to be removed from the session.
*
* @return
* - +VE : Client descriptor preceding the one being deleted
* - -1 : No descriptor preceding the one being deleted
*/
int httpd_sess_delete(struct httpd_data *hd, int clifd);
/**
* @brief Free session context
*
* @param[in] ctx Pointer to session context
* @param[in] free_fn Free function to call on session context
*/
void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn);
/**
* @brief Add descriptors present in the socket database to an fd_set and
* update the value of maxfd which are needed by the select function
* 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 Iterates through the list of client fds in the session /socket database.
* Passing the value of a client fd returns the fd for the next client
* in the database. In order to iterate from the beginning pass -1 as fd.
*
* @param[in] hd Server instance data
* @param[in] fd Last accessed client descriptor.
* -1 to reset iterator to start of database.
*
* @return
* - +VE : Client descriptor next in the database
* - -1 : End of iteration
*/
int httpd_sess_iterate(struct httpd_data *hd, int fd);
/**
* @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 unreceive 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 furtur 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] fd Client descriptor
*
* @return True if there is any pending data
*/
bool httpd_sess_pending(struct httpd_data *hd, int fd);
/**
* @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);
/** 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 Deregister 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 neds 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 defaut and enable it only if necessary */
#ifdef CONFIG_HTTPD_VALIDATE_REQ
#define httpd_valid_req(r) httpd_validate_req_ptr(r)
#else
#define httpd_valid_req(r) true
#endif
/** End of Group : URI Handling
* @}
*/
/****************** 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 error code in response to HTTP request.
*
* @param[in] req Pointer to the HTTP request for which the resonse needs to be sent
* @param[in] error Error type to send
*
* @return
* - ESP_OK : if successful
* - ESP_FAIL : if failed
*/
esp_err_t httpd_resp_send_err(httpd_req_t *req, httpd_err_resp_t error);
/**
* @brief For sending out data in response to an HTTP request.
*
* @param[in] req Pointer to the HTTP request for which the resonse 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. 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.
*
* @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] halt_after_pending When set true, halts immediatly after receiving from
* pending buffer
*
* @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, bool halt_after_pending);
/**
* @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
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ! _HTTPD_PRIV_H_ */

View File

@ -0,0 +1,403 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <errno.h>
#include <esp_log.h>
#include <esp_err.h>
#include <assert.h>
#include <esp_http_server.h>
#include "esp_httpd_priv.h"
#include "ctrl_sock.h"
static const char *TAG = "httpd";
static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd)
{
/* 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 */
return httpd_sess_close_lru(hd);
/* Returning from this allowes 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_in addr_from;
socklen_t addr_from_len = sizeof(addr_from);
int new_fd = accept(listen_fd, (struct sockaddr *)&addr_from, &addr_from_len);
if (new_fd < 0) {
ESP_LOGW(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;
setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
/* Set send timeout of this fd as per config */
tv.tv_sec = hd->config.send_wait_timeout;
tv.tv_usec = 0;
setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
if (ESP_OK != httpd_sess_new(hd, new_fd)) {
ESP_LOGW(TAG, LOG_FMT("session creation failed"));
close(new_fd);
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("complete"));
return ESP_OK;
}
struct httpd_ctrl_data {
enum httpd_ctrl_msg {
HTTPD_CTRL_SHUTDOWN,
HTTPD_CTRL_WORK,
} hc_msg;
httpd_work_fn_t hc_work;
void *hc_work_arg;
};
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,
};
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"));
return ESP_FAIL;
}
return ESP_OK;
}
void *httpd_get_global_user_ctx(httpd_handle_t handle)
{
return ((struct httpd_data *)handle)->config.global_user_ctx;
}
void *httpd_get_global_transport_ctx(httpd_handle_t handle)
{
return ((struct httpd_data *)handle)->config.global_transport_ctx;
}
static void httpd_close_all_sessions(struct httpd_data *hd)
{
int fd = -1;
while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), fd);
httpd_sess_delete(hd, fd);
close(fd);
}
}
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);
return;
}
if (ret != sizeof(msg)) {
ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
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;
}
}
/* 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);
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? */
int fd = -1;
while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) {
ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
if (httpd_sess_process(hd, fd) != ESP_OK) {
ESP_LOGD(TAG, LOG_FMT("closing socket %d"), fd);
close(fd);
/* Delete session and update fd to that
* preceding the one being deleted */
fd = httpd_sess_delete(hd, fd);
}
}
}
/* 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, hd->listen_fd) != 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_close_all_sessions(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)
{
int fd = socket(PF_INET6, SOCK_STREAM, 0);
if (fd < 0) {
ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
return ESP_FAIL;
}
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)
};
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 != NULL) {
hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
if (hd->hd_calls == NULL) {
free(hd);
return NULL;
}
hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
if (hd->hd_sd == NULL) {
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 == NULL) {
free(hd->hd_sd);
free(hd->hd_calls);
free(hd);
return NULL;
}
/* Save the configuration for this instance */
hd->config = *config;
} else {
ESP_LOGE(TAG, "mem alloc failed");
}
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(ra->resp_hdrs);
free(hd->hd_sd);
/* 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;
}
struct httpd_data *hd = httpd_create(config);
if (hd == NULL) {
/* Failed to allocate memory */
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) != ESP_OK) {
/* Failed to launch task */
httpd_delete(hd);
return ESP_ERR_HTTPD_TASK;
}
*handle = (httpd_handle_t *)hd;
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;
cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
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);
return ESP_OK;
}

View File

@ -0,0 +1,856 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <sys/param.h>
#include <esp_log.h>
#include <esp_err.h>
#include <http_parser.h>
#include <esp_http_server.h>
#include "esp_httpd_priv.h"
#include "osal.h"
static const char *TAG = "httpd_parse";
typedef struct {
/* Parser settings for http_parser_execute() */
http_parser_settings settings;
/* Request being parsed */
struct httpd_req *req;
/* Status of the parser describes the part of the
* HTTP request packet being processed at any moment.
*/
enum {
PARSING_IDLE = 0,
PARSING_URL,
PARSING_HDR_FIELD,
PARSING_HDR_VALUE,
PARSING_BODY,
PARSING_COMPLETE,
PARSING_FAILED
} status;
/* Response error code in case of PARSING_FAILED */
httpd_err_resp_t error;
/* For storing last callback parameters */
struct {
const char *at;
size_t length;
} last;
/* State variables */
bool paused; /*!< Parser is paused */
size_t pre_parsed; /*!< Length of data to be skipped while parsing */
size_t raw_datalen; /*!< Full length of the raw data in scratch buffer */
} parser_data_t;
static esp_err_t verify_url (http_parser *parser)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
struct httpd_req *r = parser_data->req;
struct httpd_req_aux *ra = r->aux;
struct http_parser_url *res = &ra->url_parse_res;
/* Get previous values of the parser callback arguments */
const char *at = parser_data->last.at;
size_t length = parser_data->last.length;
if ((r->method = parser->method) < 0) {
ESP_LOGW(TAG, LOG_FMT("HTTP Operation not supported"));
parser_data->error = HTTPD_501_METHOD_NOT_IMPLEMENTED;
return ESP_FAIL;
}
if (sizeof(r->uri) < (length + 1)) {
ESP_LOGW(TAG, LOG_FMT("URI length (%d) greater than supported (%d)"),
length, sizeof(r->uri));
parser_data->error = HTTPD_414_URI_TOO_LONG;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* Keep URI with terminating null character. Note URI string pointed
* by 'at' is not NULL terminated, therefore use length provided by
* parser while copying the URI to buffer */
strlcpy((char *)r->uri, at, (length + 1));
ESP_LOGD(TAG, LOG_FMT("received URI = %s"), r->uri);
/* Make sure version is HTTP/1.1 */
if ((parser->http_major != 1) && (parser->http_minor != 1)) {
ESP_LOGW(TAG, LOG_FMT("unsupported HTTP version = %d.%d"),
parser->http_major, parser->http_minor);
parser_data->error = HTTPD_505_VERSION_NOT_SUPPORTED;
return ESP_FAIL;
}
/* Parse URL and keep result for later */
http_parser_url_init(res);
if (http_parser_parse_url(r->uri, strlen(r->uri),
r->method == HTTP_CONNECT, res)) {
ESP_LOGW(TAG, LOG_FMT("http_parser_parse_url failed with errno = %d"),
parser->http_errno);
parser_data->error = HTTPD_400_BAD_REQUEST;
return ESP_FAIL;
}
return ESP_OK;
}
/* http_parser callback on finding url in HTTP request
* Will be invoked ATLEAST once every packet
*/
static esp_err_t cb_url(http_parser *parser,
const char *at, size_t length)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
if (parser_data->status == PARSING_IDLE) {
ESP_LOGD(TAG, LOG_FMT("message begin"));
/* Store current values of the parser callback arguments */
parser_data->last.at = at;
parser_data->last.length = 0;
parser_data->status = PARSING_URL;
} else if (parser_data->status != PARSING_URL) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("processing url = %.*s"), length, at);
/* Update length of URL string */
if ((parser_data->last.length += length) > HTTPD_MAX_URI_LEN) {
ESP_LOGW(TAG, LOG_FMT("URI length (%d) greater than supported (%d)"),
parser_data->last.length, HTTPD_MAX_URI_LEN);
parser_data->error = HTTPD_414_URI_TOO_LONG;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
return ESP_OK;
}
static esp_err_t pause_parsing(http_parser *parser, const char* at)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
struct httpd_req *r = parser_data->req;
struct httpd_req_aux *ra = r->aux;
parser_data->pre_parsed = parser_data->raw_datalen
- (at - ra->scratch);
if (parser_data->pre_parsed != httpd_unrecv(r, at, parser_data->pre_parsed)) {
ESP_LOGE(TAG, LOG_FMT("data too large for un-recv = %d"),
parser_data->pre_parsed);
return ESP_FAIL;
}
http_parser_pause(parser, 1);
parser_data->paused = true;
ESP_LOGD(TAG, LOG_FMT("paused"));
return ESP_OK;
}
static size_t continue_parsing(http_parser *parser, size_t length)
{
parser_data_t *data = (parser_data_t *) parser->data;
/* Part of the blk may have been parsed before
* so we must skip that */
length = MIN(length, data->pre_parsed);
data->pre_parsed -= length;
ESP_LOGD(TAG, LOG_FMT("skip pre-parsed data of size = %d"), length);
http_parser_pause(parser, 0);
data->paused = false;
ESP_LOGD(TAG, LOG_FMT("un-paused"));
return length;
}
/* http_parser callback on header field in HTTP request
* May be invoked ATLEAST once every header field
*/
static esp_err_t cb_header_field(http_parser *parser, const char *at, size_t length)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
struct httpd_req *r = parser_data->req;
struct httpd_req_aux *ra = r->aux;
/* Check previous status */
if (parser_data->status == PARSING_URL) {
if (verify_url(parser) != ESP_OK) {
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("headers begin"));
/* Last at is set to start of scratch where headers
* will be received next */
parser_data->last.at = ra->scratch;
parser_data->last.length = 0;
parser_data->status = PARSING_HDR_FIELD;
/* Stop parsing for now and give control to process */
if (pause_parsing(parser, at) != ESP_OK) {
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
} else if (parser_data->status == PARSING_HDR_VALUE) {
/* NULL terminate last header (key: value) pair */
size_t offset = parser_data->last.at - ra->scratch;
ra->scratch[offset + parser_data->last.length] = '\0';
/* Store current values of the parser callback arguments */
parser_data->last.at = at;
parser_data->last.length = 0;
parser_data->status = PARSING_HDR_FIELD;
} else if (parser_data->status != PARSING_HDR_FIELD) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("processing field = %.*s"), length, at);
/* Update length of header string */
parser_data->last.length += length;
return ESP_OK;
}
/* http_parser callback on header value in HTTP request.
* May be invoked ATLEAST once every header value
*/
static esp_err_t cb_header_value(http_parser *parser, const char *at, size_t length)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
struct httpd_req *r = parser_data->req;
struct httpd_req_aux *ra = r->aux;
/* Check previous status */
if (parser_data->status == PARSING_HDR_FIELD) {
/* Store current values of the parser callback arguments */
parser_data->last.at = at;
parser_data->last.length = 0;
parser_data->status = PARSING_HDR_VALUE;
/* Increment header count */
ra->req_hdrs_count++;
} else if (parser_data->status != PARSING_HDR_VALUE) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("processing value = %.*s"), length, at);
/* Update length of header string */
parser_data->last.length += length;
return ESP_OK;
}
/* http_parser callback on completing headers in HTTP request.
* Will be invoked ONLY once every packet
*/
static esp_err_t cb_headers_complete(http_parser *parser)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
struct httpd_req *r = parser_data->req;
struct httpd_req_aux *ra = r->aux;
/* Check previous status */
if (parser_data->status == PARSING_URL) {
ESP_LOGD(TAG, LOG_FMT("no headers"));
if (verify_url(parser) != ESP_OK) {
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
} else if (parser_data->status == PARSING_HDR_VALUE) {
/* NULL terminate last header (key: value) pair */
size_t offset = parser_data->last.at - ra->scratch;
ra->scratch[offset + parser_data->last.length] = '\0';
/* Reach end of last header */
parser_data->last.at += parser_data->last.length;
} else {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* In absence of body/chunked encoding, http_parser sets content_len to -1 */
r->content_len = ((int)parser->content_length != -1 ?
parser->content_length : 0);
ESP_LOGD(TAG, LOG_FMT("bytes read = %d"), parser->nread);
ESP_LOGD(TAG, LOG_FMT("content length = %zu"), r->content_len);
if (parser->upgrade) {
ESP_LOGW(TAG, LOG_FMT("upgrade from HTTP not supported"));
parser_data->error = HTTPD_XXX_UPGRADE_NOT_SUPPORTED;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
parser_data->status = PARSING_BODY;
ra->remaining_len = r->content_len;
return ESP_OK;
}
/* Last http_parser callback if body present in HTTP request.
* Will be invoked ONLY once every packet
*/
static esp_err_t cb_on_body(http_parser *parser, const char *at, size_t length)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
/* Check previous status */
if (parser_data->status != PARSING_BODY) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* Pause parsing so that if part of another packet
* is in queue then it doesn't get parsed, which
* may reset the parser state and cause current
* request packet to be lost */
if (pause_parsing(parser, at) != ESP_OK) {
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
parser_data->last.at = 0;
parser_data->last.length = 0;
parser_data->status = PARSING_COMPLETE;
ESP_LOGD(TAG, LOG_FMT("body begins"));
return ESP_OK;
}
/* Last http_parser callback if body absent in HTTP request.
* Will be invoked ONLY once every packet
*/
static esp_err_t cb_no_body(http_parser *parser)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
const char* at = parser_data->last.at;
/* Check previous status */
if (parser_data->status == PARSING_URL) {
ESP_LOGD(TAG, LOG_FMT("no headers"));
if (verify_url(parser) != ESP_OK) {
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
} else if (parser_data->status != PARSING_BODY) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* Get end of packet */
at += strlen("\r\n\r\n");
/* Pause parsing so that if part of another packet
* is in queue then it doesn't get parsed, which
* may reset the parser state and cause current
* request packet to be lost */
if (pause_parsing(parser, at) != ESP_OK) {
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
parser_data->last.at = 0;
parser_data->last.length = 0;
parser_data->status = PARSING_COMPLETE;
ESP_LOGD(TAG, LOG_FMT("message complete"));
return ESP_OK;
}
static int read_block(httpd_req_t *req, size_t offset, size_t length)
{
struct httpd_req_aux *raux = req->aux;
/* Limits the read to scratch buffer size */
size_t buf_len = MIN(length, (sizeof(raux->scratch) - offset));
if (buf_len == 0) {
return 0;
}
/* Receive data into buffer. If data is pending (from unrecv) then return
* immediately after receiving pending data, as pending data may just complete
* this request packet. */
int nbytes = httpd_recv_with_opt(req, raux->scratch + offset, buf_len, true);
if (nbytes < 0) {
ESP_LOGD(TAG, LOG_FMT("error in httpd_recv"));
if (nbytes == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_err(req, HTTPD_408_REQ_TIMEOUT);
}
return -1;
} else if (nbytes == 0) {
ESP_LOGD(TAG, LOG_FMT("connection closed"));
return -1;
}
ESP_LOGD(TAG, LOG_FMT("received HTTP request block size = %d"), nbytes);
return nbytes;
}
static int parse_block(http_parser *parser, size_t offset, size_t length)
{
parser_data_t *data = (parser_data_t *)(parser->data);
httpd_req_t *req = data->req;
struct httpd_req_aux *raux = req->aux;
size_t nparsed = 0;
if (!length) {
ESP_LOGW(TAG, LOG_FMT("response uri/header too big"));
switch (data->status) {
case PARSING_URL:
data->error = HTTPD_414_URI_TOO_LONG;
break;
case PARSING_HDR_FIELD:
case PARSING_HDR_VALUE:
data->error = HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE;
default:
break;
}
data->status = PARSING_FAILED;
return -1;
}
/* Unpause the parsing if paused */
if (data->paused) {
nparsed = continue_parsing(parser, length);
length -= nparsed;
offset += nparsed;
if (!length) {
return nparsed;
}
}
/* Execute http_parser */
nparsed = http_parser_execute(parser, &data->settings,
raux->scratch + offset, length);
/* Check state */
if (data->status == PARSING_FAILED) {
ESP_LOGW(TAG, LOG_FMT("parsing failed"));
return -1;
} else if (data->paused) {
/* Keep track of parsed data to be skipped
* during next parsing cycle */
data->pre_parsed -= (length - nparsed);
return 0;
} else if (nparsed != length) {
/* http_parser error */
data->status = PARSING_FAILED;
data->error = HTTPD_400_BAD_REQUEST;
ESP_LOGW(TAG, LOG_FMT("incomplete (%d/%d) with parser error = %d"),
nparsed, length, parser->http_errno);
return -1;
}
/* Continue parsing this section of HTTP request packet */
ESP_LOGD(TAG, LOG_FMT("parsed block size = %d"), offset + nparsed);
return offset + nparsed;
}
static void parse_init(httpd_req_t *r, http_parser *parser, parser_data_t *data)
{
/* Initialize parser data */
memset(data, 0, sizeof(parser_data_t));
data->req = r;
/* Initialize parser */
http_parser_init(parser, HTTP_REQUEST);
parser->data = (void *)data;
/* Initialize parser settings */
http_parser_settings_init(&data->settings);
/* Set parser callbacks */
data->settings.on_url = cb_url;
data->settings.on_header_field = cb_header_field;
data->settings.on_header_value = cb_header_value;
data->settings.on_headers_complete = cb_headers_complete;
data->settings.on_body = cb_on_body;
data->settings.on_message_complete = cb_no_body;
}
/* Function that receives TCP data and runs parser on it
*/
static esp_err_t httpd_parse_req(struct httpd_data *hd)
{
httpd_req_t *r = &hd->hd_req;
int blk_len, offset;
http_parser parser;
parser_data_t parser_data;
/* Initialize parser */
parse_init(r, &parser, &parser_data);
/* Set offset to start of scratch buffer */
offset = 0;
do {
/* Read block into scratch buffer */
if ((blk_len = read_block(r, offset, PARSER_BLOCK_SIZE)) < 0) {
/* Return error to close socket */
return ESP_FAIL;
}
/* This is used by the callbacks to track
* data usage of the buffer */
parser_data.raw_datalen = blk_len + offset;
/* Parse data block from buffer */
if ((offset = parse_block(&parser, offset, blk_len)) < 0) {
/* Server/Client error. Send error code as response status */
return httpd_resp_send_err(r, parser_data.error);
}
} while (parser_data.status != PARSING_COMPLETE);
ESP_LOGD(TAG, LOG_FMT("parsing complete"));
return httpd_uri(hd);
}
static void init_req(httpd_req_t *r, httpd_config_t *config)
{
r->handle = 0;
r->method = 0;
memset((char*)r->uri, 0, sizeof(r->uri));
r->content_len = 0;
r->aux = 0;
r->user_ctx = 0;
r->sess_ctx = 0;
r->free_ctx = 0;
}
static void init_req_aux(struct httpd_req_aux *ra, httpd_config_t *config)
{
ra->sd = 0;
memset(ra->scratch, 0, sizeof(ra->scratch));
ra->remaining_len = 0;
ra->status = 0;
ra->content_type = 0;
ra->first_chunk_sent = 0;
ra->req_hdrs_count = 0;
ra->resp_hdrs_count = 0;
memset(ra->resp_hdrs, 0, config->max_resp_headers * sizeof(struct resp_hdr));
}
static void httpd_req_cleanup(httpd_req_t *r)
{
struct httpd_req_aux *ra = r->aux;
/* Retrieve session info from the request into the socket database */
if (ra->sd->ctx != r->sess_ctx) {
/* Free previous context */
httpd_sess_free_ctx(ra->sd->ctx, ra->sd->free_ctx);
ra->sd->ctx = r->sess_ctx;
}
ra->sd->free_ctx = r->free_ctx;
/* Clear out the request and request_aux structures */
ra->sd = NULL;
r->handle = NULL;
r->aux = NULL;
}
/* Function that processes incoming TCP data and
* updates the http request data httpd_req_t
*/
esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd)
{
httpd_req_t *r = &hd->hd_req;
init_req(r, &hd->config);
init_req_aux(&hd->hd_req_aux, &hd->config);
r->handle = hd;
r->aux = &hd->hd_req_aux;
/* Associate the request to the socket */
struct httpd_req_aux *ra = r->aux;
ra->sd = sd;
/* Set defaults */
ra->status = (char *)HTTPD_200;
ra->content_type = (char *)HTTPD_TYPE_TEXT;
ra->first_chunk_sent = false;
/* Copy session info to the request */
r->sess_ctx = sd->ctx;
r->free_ctx = sd->free_ctx;
/* Parse request */
esp_err_t err = httpd_parse_req(hd);
if (err != ESP_OK) {
httpd_req_cleanup(r);
}
return err;
}
/* Function that resets the http request data
*/
esp_err_t httpd_req_delete(struct httpd_data *hd)
{
httpd_req_t *r = &hd->hd_req;
struct httpd_req_aux *ra = r->aux;
/* Finish off reading any pending/leftover data */
while (ra->remaining_len) {
/* Any length small enough not to overload the stack, but large
* enough to finish off the buffers fast
*/
char dummy[32];
int recv_len = MIN(sizeof(dummy) - 1, ra->remaining_len);
int ret = httpd_req_recv(r, dummy, recv_len);
if (ret < 0) {
httpd_req_cleanup(r);
return ESP_FAIL;
}
dummy[ret] = '\0';
ESP_LOGD(TAG, LOG_FMT("purging data : %s"), dummy);
}
httpd_req_cleanup(r);
return ESP_OK;
}
/* Validates the request to prevent users from calling APIs, that are to
* be called only inside URI handler, outside the handler context
*/
bool httpd_validate_req_ptr(httpd_req_t *r)
{
if (r) {
struct httpd_data *hd = (struct httpd_data *) r->handle;
if (hd) {
/* Check if this function is running in the context of
* the correct httpd server thread */
if (httpd_os_thread_handle() == hd->hd_td.handle) {
return true;
}
}
}
return false;
}
/* Helper function to get a URL query tag from a query string of the type param1=val1&param2=val2 */
esp_err_t httpd_query_key_value(const char *qry_str, const char *key, char *val, size_t val_size)
{
if (qry_str == NULL || key == NULL || val == NULL) {
return ESP_ERR_INVALID_ARG;
}
const char *qry_ptr = qry_str;
const size_t buf_len = val_size;
while (strlen(qry_ptr)) {
/* Search for the '=' character. Else, it would mean
* that the parameter is invalid */
const char *val_ptr = strchr(qry_ptr, '=');
if (!val_ptr) {
break;
}
size_t offset = val_ptr - qry_ptr;
/* If the key, does not match, continue searching.
* Compare lengths first as key from url is not
* null terminated (has '=' in the end) */
if ((offset != strlen(key)) ||
(strncasecmp(qry_ptr, key, offset))) {
/* Get the name=val string. Multiple name=value pairs
* are separated by '&' */
qry_ptr = strchr(val_ptr, '&');
if (!qry_ptr) {
break;
}
qry_ptr++;
continue;
}
/* Locate start of next query */
qry_ptr = strchr(++val_ptr, '&');
/* Or this could be the last query, in which
* case get to the end of query string */
if (!qry_ptr) {
qry_ptr = val_ptr + strlen(val_ptr);
}
/* Update value length, including one byte for null */
val_size = qry_ptr - val_ptr + 1;
/* Copy value to the caller's buffer. */
strlcpy(val, val_ptr, MIN(val_size, buf_len));
/* If buffer length is smaller than needed, return truncation error */
if (buf_len < val_size) {
return ESP_ERR_HTTPD_RESULT_TRUNC;
}
return ESP_OK;
}
ESP_LOGD(TAG, LOG_FMT("key %s not found"), key);
return ESP_ERR_NOT_FOUND;
}
size_t httpd_req_get_url_query_len(httpd_req_t *r)
{
if (r == NULL) {
return 0;
}
if (!httpd_valid_req(r)) {
return 0;
}
struct httpd_req_aux *ra = r->aux;
struct http_parser_url *res = &ra->url_parse_res;
/* Check if query field is present in the URL */
if (res->field_set & (1 << UF_QUERY)) {
return res->field_data[UF_QUERY].len;
}
return 0;
}
esp_err_t httpd_req_get_url_query_str(httpd_req_t *r, char *buf, size_t buf_len)
{
if (r == NULL || buf == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!httpd_valid_req(r)) {
return ESP_ERR_HTTPD_INVALID_REQ;
}
struct httpd_req_aux *ra = r->aux;
struct http_parser_url *res = &ra->url_parse_res;
/* Check if query field is present in the URL */
if (res->field_set & (1 << UF_QUERY)) {
const char *qry = r->uri + res->field_data[UF_QUERY].off;
/* Minimum required buffer len for keeping
* null terminated query string */
size_t min_buf_len = res->field_data[UF_QUERY].len + 1;
strlcpy(buf, qry, MIN(buf_len, min_buf_len));
if (buf_len < min_buf_len) {
return ESP_ERR_HTTPD_RESULT_TRUNC;
}
return ESP_OK;
}
return ESP_ERR_NOT_FOUND;
}
/* Get the length of the value string of a header request field */
size_t httpd_req_get_hdr_value_len(httpd_req_t *r, const char *field)
{
if (r == NULL || field == NULL) {
return 0;
}
if (!httpd_valid_req(r)) {
return 0;
}
struct httpd_req_aux *ra = r->aux;
const char *hdr_ptr = ra->scratch; /*!< Request headers are kept in scratch buffer */
unsigned count = ra->req_hdrs_count; /*!< Count set during parsing */
while (count--) {
/* Search for the ':' character. Else, it would mean
* that the field is invalid
*/
const char *val_ptr = strchr(hdr_ptr, ':');
if (!val_ptr) {
break;
}
/* If the field, does not match, continue searching.
* Compare lengths first as field from header is not
* null terminated (has ':' in the end).
*/
if ((val_ptr - hdr_ptr != strlen(field)) ||
(strncasecmp(hdr_ptr, field, strlen(field)))) {
hdr_ptr += strlen(hdr_ptr) + strlen("\r\n");
continue;
}
/* Skip ':' */
val_ptr++;
/* Skip preceding space */
while ((*val_ptr != '\0') && (*val_ptr == ' ')) {
val_ptr++;
}
return strlen(val_ptr);
}
return 0;
}
/* Get the value of a field from the request headers */
esp_err_t httpd_req_get_hdr_value_str(httpd_req_t *r, const char *field, char *val, size_t val_size)
{
if (r == NULL || field == 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 *hdr_ptr = ra->scratch; /*!< Request headers are kept in scratch buffer */
unsigned count = ra->req_hdrs_count; /*!< Count set during parsing */
const size_t buf_len = val_size;
while (count--) {
/* Search for the ':' character. Else, it would mean
* that the field is invalid
*/
const char *val_ptr = strchr(hdr_ptr, ':');
if (!val_ptr) {
break;
}
/* If the field, does not match, continue searching.
* Compare lengths first as field from header is not
* null terminated (has ':' in the end).
*/
if ((val_ptr - hdr_ptr != strlen(field)) ||
(strncasecmp(hdr_ptr, field, strlen(field)))) {
hdr_ptr += strlen(hdr_ptr) + strlen("\r\n");
continue;
}
/* Skip ':' */
val_ptr++;
/* Skip preceding space */
while ((*val_ptr != '\0') && (*val_ptr == ' ')) {
val_ptr++;
}
/* Get the NULL terminated value and copy it to the caller's buffer. */
strlcpy(val, val_ptr, buf_len);
/* Update value length, including one byte for null */
val_size = strlen(val_ptr) + 1;
/* If buffer length is smaller than needed, return truncation error */
if (buf_len < val_size) {
return ESP_ERR_HTTPD_RESULT_TRUNC;
}
return ESP_OK;
}
return ESP_ERR_NOT_FOUND;
}

View File

@ -0,0 +1,386 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_http_server.h>
#include "esp_httpd_priv.h"
static const char *TAG = "httpd_sess";
bool httpd_is_sess_available(struct httpd_data *hd)
{
int i;
for (i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd == -1) {
return true;
}
}
return false;
}
struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
{
if (hd == NULL) {
return NULL;
}
/* Check if called inside a request handler, and the
* session sockfd in use is same as the parameter */
if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
/* Just return the pointer to the sock_db
* corresponding to the request */
return hd->hd_req_aux.sd;
}
int i;
for (i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd == sockfd) {
return &hd->hd_sd[i];
}
}
return NULL;
}
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;
}
int i;
for (i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd == -1) {
memset(&hd->hd_sd[i], 0, sizeof(hd->hd_sd[i]));
hd->hd_sd[i].fd = newfd;
hd->hd_sd[i].handle = (httpd_handle_t) hd;
hd->hd_sd[i].send_fn = httpd_default_send;
hd->hd_sd[i].recv_fn = httpd_default_recv;
/* Call user-defined session opening function */
if (hd->config.open_fn) {
esp_err_t ret = hd->config.open_fn(hd, hd->hd_sd[i].fd);
if (ret != ESP_OK) return ret;
}
return ESP_OK;
}
}
ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
return ESP_FAIL;
}
void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn)
{
if (ctx) {
if (free_fn) {
free_fn(ctx);
} else {
free(ctx);
}
}
}
void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
{
struct sock_db *sd = httpd_sess_get(handle, sockfd);
if (sd == NULL) {
return NULL;
}
/* Check if the function has been called from inside a
* request handler, in which case fetch the context from
* the httpd_req_t structure */
struct httpd_data *hd = (struct httpd_data *) handle;
if (hd->hd_req_aux.sd == sd) {
return hd->hd_req.sess_ctx;
}
return sd->ctx;
}
void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
{
struct sock_db *sd = httpd_sess_get(handle, sockfd);
if (sd == NULL) {
return;
}
/* Check if the function has been called from inside a
* request handler, in which case set the context inside
* the httpd_req_t structure */
struct httpd_data *hd = (struct httpd_data *) handle;
if (hd->hd_req_aux.sd == sd) {
if (hd->hd_req.sess_ctx != ctx) {
/* Don't free previous context if it is in sockdb
* as it will be freed inside httpd_req_cleanup() */
if (sd->ctx != hd->hd_req.sess_ctx) {
/* Free previous context */
httpd_sess_free_ctx(hd->hd_req.sess_ctx, hd->hd_req.free_ctx);
}
hd->hd_req.sess_ctx = ctx;
}
hd->hd_req.free_ctx = free_fn;
return;
}
/* Else set the context inside the sock_db structure */
if (sd->ctx != ctx) {
/* Free previous context */
httpd_sess_free_ctx(sd->ctx, sd->free_ctx);
sd->ctx = ctx;
}
sd->free_ctx = free_fn;
}
void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
{
struct sock_db *sd = httpd_sess_get(handle, sockfd);
if (sd == NULL) {
return NULL;
}
return sd->transport_ctx;
}
void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
{
struct sock_db *sd = httpd_sess_get(handle, sockfd);
if (sd == NULL) {
return;
}
if (sd->transport_ctx != ctx) {
/* Free previous transport context */
httpd_sess_free_ctx(sd->transport_ctx, sd->free_transport_ctx);
sd->transport_ctx = ctx;
}
sd->free_transport_ctx = free_fn;
}
void httpd_sess_set_descriptors(struct httpd_data *hd,
fd_set *fdset, int *maxfd)
{
int i;
*maxfd = -1;
for (i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd != -1) {
FD_SET(hd->hd_sd[i].fd, fdset);
if (hd->hd_sd[i].fd > *maxfd) {
*maxfd = hd->hd_sd[i].fd;
}
}
}
}
/** Check if a FD is valid */
static int fd_is_valid(int fd)
{
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}
void httpd_sess_delete_invalid(struct httpd_data *hd)
{
for (int i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd != -1 && !fd_is_valid(hd->hd_sd[i].fd)) {
ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), hd->hd_sd[i].fd);
httpd_sess_delete(hd, hd->hd_sd[i].fd);
}
}
}
int httpd_sess_delete(struct httpd_data *hd, int fd)
{
ESP_LOGD(TAG, LOG_FMT("fd = %d"), fd);
int i;
int pre_sess_fd = -1;
for (i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd == fd) {
/* global close handler */
if (hd->config.close_fn) {
hd->config.close_fn(hd, fd);
}
/* release 'user' context */
if (hd->hd_sd[i].ctx) {
if (hd->hd_sd[i].free_ctx) {
hd->hd_sd[i].free_ctx(hd->hd_sd[i].ctx);
} else {
free(hd->hd_sd[i].ctx);
}
hd->hd_sd[i].ctx = NULL;
hd->hd_sd[i].free_ctx = NULL;
}
/* release 'transport' context */
if (hd->hd_sd[i].transport_ctx) {
if (hd->hd_sd[i].free_transport_ctx) {
hd->hd_sd[i].free_transport_ctx(hd->hd_sd[i].transport_ctx);
} else {
free(hd->hd_sd[i].transport_ctx);
}
hd->hd_sd[i].transport_ctx = NULL;
hd->hd_sd[i].free_transport_ctx = NULL;
}
/* mark session slot as available */
hd->hd_sd[i].fd = -1;
break;
} else if (hd->hd_sd[i].fd != -1) {
/* Return the fd just preceding the one being
* deleted so that iterator can continue from
* the correct fd */
pre_sess_fd = hd->hd_sd[i].fd;
}
}
return pre_sess_fd;
}
void httpd_sess_init(struct httpd_data *hd)
{
int i;
for (i = 0; i < hd->config.max_open_sockets; i++) {
hd->hd_sd[i].fd = -1;
hd->hd_sd[i].ctx = NULL;
}
}
bool httpd_sess_pending(struct httpd_data *hd, int fd)
{
struct sock_db *sd = httpd_sess_get(hd, fd);
if (! sd) {
return ESP_FAIL;
}
if (sd->pending_fn) {
// test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
// this should check e.g. for the SSL data buffer
if (sd->pending_fn(hd, fd) > 0) return true;
}
return (sd->pending_len != 0);
}
/* 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, int newfd)
{
struct sock_db *sd = httpd_sess_get(hd, newfd);
if (! sd) {
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
if (httpd_req_new(hd, sd) != 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"));
sd->timestamp = httpd_os_get_timestamp();
return ESP_OK;
}
esp_err_t httpd_sess_update_timestamp(httpd_handle_t handle, int sockfd)
{
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
/* Search for the socket database entry */
struct httpd_data *hd = (struct httpd_data *) handle;
int i;
for (i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd == sockfd) {
hd->hd_sd[i].timestamp = httpd_os_get_timestamp();
return ESP_OK;
}
}
return ESP_ERR_NOT_FOUND;
}
esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
{
int64_t timestamp = INT64_MAX;
int lru_fd = -1;
int i;
for (i = 0; i < hd->config.max_open_sockets; i++) {
/* If a descriptor is -1, there is no need to close any session.
* So, we can return from here, without finding the Least Recently Used
* session
*/
if (hd->hd_sd[i].fd == -1) {
return ESP_OK;
}
if (hd->hd_sd[i].timestamp < timestamp) {
timestamp = hd->hd_sd[i].timestamp;
lru_fd = hd->hd_sd[i].fd;
}
}
ESP_LOGD(TAG, LOG_FMT("fd = %d"), lru_fd);
return httpd_sess_trigger_close(hd, lru_fd);
}
int httpd_sess_iterate(struct httpd_data *hd, int start_fd)
{
int start_index = 0;
int i;
if (start_fd != -1) {
/* Take our index to where this fd is stored */
for (i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd == start_fd) {
start_index = i + 1;
break;
}
}
}
for (i = start_index; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd != -1) {
return hd->hd_sd[i].fd;
}
}
return -1;
}
static void httpd_sess_close(void *arg)
{
struct sock_db *sock_db = (struct sock_db *)arg;
if (sock_db) {
int fd = sock_db->fd;
struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
httpd_sess_delete(hd, fd);
close(fd);
}
}
esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
{
struct sock_db *sock_db = httpd_sess_get(handle, sockfd);
if (sock_db) {
return httpd_queue_work(handle, httpd_sess_close, sock_db);
}
return ESP_ERR_NOT_FOUND;
}

View File

@ -0,0 +1,553 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_http_server.h>
#include "esp_httpd_priv.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, bool halt_after_pending)
{
ESP_LOGD(TAG, LOG_FMT("requested length = %d"), 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 = %d"), 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 || halt_after_pending) {
return pending_len;
}
}
/* Receive data of remaining length */
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;
}
ESP_LOGD(TAG, LOG_FMT("received length = %d"), ret + pending_len);
return ret + pending_len;
}
int httpd_recv(httpd_req_t *r, char *buf, size_t buf_len)
{
return httpd_recv_with_opt(r, buf, buf_len, false);
}
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 */
size_t offset = sizeof(ra->sd->pending_data) - ra->sd->pending_len;
memcpy(ra->sd->pending_data + offset, buf, buf_len);
ESP_LOGD(TAG, LOG_FMT("length = %d"), 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;
}
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;
}
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;
}
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 == -1) buf_len = strlen(buf);
/* Request headers are no longer available */
ra->req_hdrs_count = 0;
/* Size of essential headers is limited by scratch buffer size */
if (snprintf(ra->scratch, sizeof(ra->scratch), httpd_hdr_str,
ra->status, ra->content_type, buf_len) >= sizeof(ra->scratch)) {
return ESP_ERR_HTTPD_RESP_HDR;
}
/* Sending essential headers */
if (httpd_send_all(r, ra->scratch, strlen(ra->scratch)) != 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;
}
/* Sending content */
if (buf && buf_len) {
if (httpd_send_all(r, buf, buf_len) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
}
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 == -1) buf_len = strlen(buf);
struct httpd_req_aux *ra = r->aux;
const char *httpd_chunked_hdr_str = "HTTP/1.1 %s\r\nContent-Type: %s\r\nTransfer-Encoding: chunked\r\n";
const char *colon_separator = ": ";
const char *cr_lf_seperator = "\r\n";
/* Request headers are no longer available */
ra->req_hdrs_count = 0;
if (!ra->first_chunk_sent) {
/* Size of essential headers is limited by scratch buffer size */
if (snprintf(ra->scratch, sizeof(ra->scratch), httpd_chunked_hdr_str,
ra->status, ra->content_type) >= sizeof(ra->scratch)) {
return ESP_ERR_HTTPD_RESP_HDR;
}
/* Sending essential headers */
if (httpd_send_all(r, ra->scratch, strlen(ra->scratch)) != 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), "%x\r\n", 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;
}
return ESP_OK;
}
esp_err_t httpd_resp_send_404(httpd_req_t *r)
{
return httpd_resp_send_err(r, HTTPD_404_NOT_FOUND);
}
esp_err_t httpd_resp_send_408(httpd_req_t *r)
{
return httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT);
}
esp_err_t httpd_resp_send_500(httpd_req_t *r)
{
return httpd_resp_send_err(r, HTTPD_500_SERVER_ERROR);
}
esp_err_t httpd_resp_send_err(httpd_req_t *req, httpd_err_resp_t error)
{
const char *msg;
const char *status;
switch (error) {
case HTTPD_501_METHOD_NOT_IMPLEMENTED:
status = "501 Method Not Implemented";
msg = "Request method is not supported by server";
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 = "Server unable to understand request due to invalid syntax";
break;
case HTTPD_404_NOT_FOUND:
status = "404 Not Found";
msg = "This URI doesn't exist";
break;
case HTTPD_405_METHOD_NOT_ALLOWED:
status = "405 Method Not Allowed";
msg = "Request method for this URI is not handled by server";
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 for server to interpret";
break;
case HTTPD_411_LENGTH_REQUIRED:
status = "411 Length Required";
msg = "Chunked encoding not supported by server";
break;
case HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE:
status = "431 Request Header Fields Too Large";
msg = "Header fields are too long for server to interpret";
break;
case HTTPD_XXX_UPGRADE_NOT_SUPPORTED:
/* If the server does not support upgrade, or is unable to upgrade
* it responds with a standard HTTP/1.1 response */
status = "200 OK";
msg = "Upgrade not supported by server";
break;
case HTTPD_500_SERVER_ERROR:
default:
status = "500 Server Error";
msg = "Server has encountered an unexpected error";
}
ESP_LOGW(TAG, LOG_FMT("%s - %s"), status, msg);
httpd_resp_set_status (req, status);
httpd_resp_set_type (req, HTTPD_TYPE_TEXT);
return httpd_resp_send (req, msg, strlen(msg));
}
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 = %d"), 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);
return ret;
}
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;
int sock_err;
size_t sock_err_len = sizeof(sock_err);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sock_err, &sock_err_len) < 0) {
ESP_LOGE(TAG, LOG_FMT("error calling getsockopt : %d"), errno);
return HTTPD_SOCK_ERR_FAIL;
}
ESP_LOGW(TAG, LOG_FMT("error in %s : %d"), ctx, sock_err);
switch(sock_err) {
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;
}

View File

@ -0,0 +1,224 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <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 int httpd_find_uri_handler(struct httpd_data *hd,
const char* uri,
httpd_method_t method)
{
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
if (hd->hd_calls[i]) {
ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri);
if ((hd->hd_calls[i]->method == method) && // First match methods
(strcmp(hd->hd_calls[i]->uri, uri) == 0)) { // Then match uri strings
return i;
}
}
}
return -1;
}
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 same URI and method
* is not already registered
*/
if (httpd_find_uri_handler(handle, uri_handler->uri,
uri_handler->method) != -1) {
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) {
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;
}
/* 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]);
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;
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;
int i = httpd_find_uri_handler(hd, uri, method);
if (i != -1) {
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
free((char*)hd->hd_calls[i]->uri);
free(hd->hd_calls[i]);
hd->hd_calls[i] = 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;
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
if ((hd->hd_calls[i] != NULL) &&
(strcmp(hd->hd_calls[i]->uri, uri) == 0)) {
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, uri);
free((char*)hd->hd_calls[i]->uri);
free(hd->hd_calls[i]);
hd->hd_calls[i] = NULL;
found = true;
}
}
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]) {
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
free((char*)hd->hd_calls[i]->uri);
free(hd->hd_calls[i]);
}
}
}
/* Alternate implmentation of httpd_find_uri_handler()
* which takes a uri_len field. This is useful when the URI
* string contains extra parameters that are not to be included
* while matching with the registered URI_handler strings
*/
static httpd_uri_t* httpd_find_uri_handler2(httpd_err_resp_t *err,
struct httpd_data *hd,
const char *uri, size_t uri_len,
httpd_method_t method)
{
*err = 0;
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
if (hd->hd_calls[i]) {
ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri);
if ((strlen(hd->hd_calls[i]->uri) == uri_len) && // First match uri length
(strncmp(hd->hd_calls[i]->uri, uri, uri_len) == 0)) { // Then match uri strings
if (hd->hd_calls[i]->method == method) { // Finally match methods
return hd->hd_calls[i];
}
/* URI found but method not allowed.
* If URI IS found later then this
* error is to be neglected */
*err = HTTPD_405_METHOD_NOT_ALLOWED;
}
}
}
if (*err == 0) {
*err = HTTPD_404_NOT_FOUND;
}
return 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_resp_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_handler2(&err, hd,
req->uri + res->field_data[UF_PATH].off,
res->field_data[UF_PATH].len,
req->method);
}
/* 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_resp_send_err(req, HTTPD_404_NOT_FOUND);
case HTTPD_405_METHOD_NOT_ALLOWED:
ESP_LOGW(TAG, LOG_FMT("Method '%d' not allowed for URI '%s'"), req->method, req->uri);
return httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED);
default:
return ESP_FAIL;
}
}
/* Attach user context data (passed during URI registration) into request */
req->user_ctx = uri->user_ctx;
/* 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;
}

View File

@ -0,0 +1,69 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _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)
{
int ret = xTaskCreate(thread_routine, name, stacksize, arg, prio, thread);
if (ret == pdPASS) {
return OS_SUCCESS;
}
return OS_FAIL;
}
/* Only self delete is supported */
static inline void httpd_os_thread_delete()
{
vTaskDelete(xTaskGetCurrentTaskHandle());
}
static inline void httpd_os_thread_sleep(int msecs)
{
vTaskDelay(msecs / portTICK_RATE_MS);
}
static inline int64_t httpd_os_get_timestamp()
{
return esp_timer_get_time();
}
static inline othread_t httpd_os_thread_handle()
{
return xTaskGetCurrentTaskHandle();
}
#ifdef __cplusplus
}
#endif
#endif /* ! _OSAL_H_ */

View File

@ -0,0 +1,77 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "ctrl_sock.h"
/* Control socket, because in some network stacks select can't be woken up any
* other way
*/
int cs_create_ctrl_sock(int port)
{
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (fd < 0) {
return -1;
}
int ret;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_aton("127.0.0.1", &addr.sin_addr);
ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
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_in to_addr;
to_addr.sin_family = AF_INET;
to_addr.sin_port = htons(port);
inet_aton("127.0.0.1", &to_addr.sin_addr);
ret = sendto(send_fd, data, data_len, 0, (struct sockaddr *)&to_addr, sizeof(to_addr));
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;
}

View File

@ -0,0 +1,99 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* \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_ */