mirror of
https://github.com/espressif/ESP8266_RTOS_SDK.git
synced 2025-05-30 15:14:56 +08:00
feature/esp_http_server_idf_v3.2: Added the esp_http_server component from idf.
This commit is contained in:
531
components/esp_http_server/src/esp_httpd_priv.h
Normal file
531
components/esp_http_server/src/esp_httpd_priv.h
Normal 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_ */
|
403
components/esp_http_server/src/httpd_main.c
Normal file
403
components/esp_http_server/src/httpd_main.c
Normal 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;
|
||||
}
|
856
components/esp_http_server/src/httpd_parse.c
Normal file
856
components/esp_http_server/src/httpd_parse.c
Normal 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¶m2=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;
|
||||
}
|
386
components/esp_http_server/src/httpd_sess.c
Normal file
386
components/esp_http_server/src/httpd_sess.c
Normal 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;
|
||||
}
|
553
components/esp_http_server/src/httpd_txrx.c
Normal file
553
components/esp_http_server/src/httpd_txrx.c
Normal 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;
|
||||
}
|
224
components/esp_http_server/src/httpd_uri.c
Normal file
224
components/esp_http_server/src/httpd_uri.c
Normal 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;
|
||||
}
|
69
components/esp_http_server/src/port/esp32/osal.h
Normal file
69
components/esp_http_server/src/port/esp32/osal.h
Normal 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_ */
|
77
components/esp_http_server/src/util/ctrl_sock.c
Normal file
77
components/esp_http_server/src/util/ctrl_sock.c
Normal 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;
|
||||
}
|
99
components/esp_http_server/src/util/ctrl_sock.h
Normal file
99
components/esp_http_server/src/util/ctrl_sock.h
Normal 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_ */
|
Reference in New Issue
Block a user