mirror of
https://github.com/espressif/ESP8266_RTOS_SDK.git
synced 2025-06-28 04:42:11 +08:00
feature/esp_http_server_idf_v3.2: Added the esp_http_server component from idf.
This commit is contained in:
13
components/esp_http_server/CMakeLists.txt
Normal file
13
components/esp_http_server/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
set(COMPONENT_ADD_INCLUDEDIRS include)
|
||||||
|
set(COMPONENT_PRIV_INCLUDEDIRS src/port/esp32 src/util)
|
||||||
|
set(COMPONENT_SRCS "src/httpd_main.c"
|
||||||
|
"src/httpd_parse.c"
|
||||||
|
"src/httpd_sess.c"
|
||||||
|
"src/httpd_txrx.c"
|
||||||
|
"src/httpd_uri.c"
|
||||||
|
"src/util/ctrl_sock.c")
|
||||||
|
|
||||||
|
set(COMPONENT_REQUIRES nghttp) # for http_parser.h
|
||||||
|
set(COMPONENT_PRIV_REQUIRES lwip)
|
||||||
|
|
||||||
|
register_component()
|
15
components/esp_http_server/Kconfig
Normal file
15
components/esp_http_server/Kconfig
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
menu "HTTP Server"
|
||||||
|
|
||||||
|
config HTTPD_MAX_REQ_HDR_LEN
|
||||||
|
int "Max HTTP Request Header Length"
|
||||||
|
default 512
|
||||||
|
help
|
||||||
|
This sets the maximum supported size of headers section in HTTP request packet to be processed by the server
|
||||||
|
|
||||||
|
config HTTPD_MAX_URI_LEN
|
||||||
|
int "Max HTTP URI Length"
|
||||||
|
default 512
|
||||||
|
help
|
||||||
|
This sets the maximum supported size of HTTP request URI to be processed by the server
|
||||||
|
|
||||||
|
endmenu
|
4
components/esp_http_server/component.mk
Normal file
4
components/esp_http_server/component.mk
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
COMPONENT_SRCDIRS := src src/util
|
||||||
|
COMPONENT_ADD_INCLUDEDIRS := include
|
||||||
|
COMPONENT_PRIV_INCLUDEDIRS := src/port/esp32 src/util
|
1188
components/esp_http_server/include/esp_http_server.h
Normal file
1188
components/esp_http_server/include/esp_http_server.h
Normal file
File diff suppressed because it is too large
Load Diff
2
components/esp_http_server/include/http_server.h
Normal file
2
components/esp_http_server/include/http_server.h
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#warning http_server.h has been renamed to esp_http_server.h, please update include directives
|
||||||
|
#include "esp_http_server.h"
|
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_ */
|
6
components/esp_http_server/test/CMakeLists.txt
Normal file
6
components/esp_http_server/test/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
set(COMPONENT_SRCDIRS ".")
|
||||||
|
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||||
|
|
||||||
|
set(COMPONENT_REQUIRES unity esp_http_server)
|
||||||
|
|
||||||
|
register_component()
|
1
components/esp_http_server/test/component.mk
Normal file
1
components/esp_http_server/test/component.mk
Normal file
@ -0,0 +1 @@
|
|||||||
|
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
|
169
components/esp_http_server/test/test_http_server.c
Normal file
169
components/esp_http_server/test/test_http_server.c
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// 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 <stdbool.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
|
||||||
|
#include "unity.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
int pre_start_mem, post_stop_mem, post_stop_min_mem;
|
||||||
|
bool basic_sanity = true;
|
||||||
|
|
||||||
|
esp_err_t null_func(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_uri_t handler_limit_uri (char* path)
|
||||||
|
{
|
||||||
|
httpd_uri_t uri = {
|
||||||
|
.uri = path,
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = null_func,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
};
|
||||||
|
return uri;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline unsigned num_digits(unsigned x)
|
||||||
|
{
|
||||||
|
unsigned digits = 1;
|
||||||
|
while ((x = x/10) != 0) {
|
||||||
|
digits++;
|
||||||
|
}
|
||||||
|
return digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define HTTPD_TEST_MAX_URI_HANDLERS 8
|
||||||
|
|
||||||
|
void test_handler_limit(httpd_handle_t hd)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
char x[HTTPD_TEST_MAX_URI_HANDLERS+1][num_digits(HTTPD_TEST_MAX_URI_HANDLERS)+1];
|
||||||
|
httpd_uri_t uris[HTTPD_TEST_MAX_URI_HANDLERS+1];
|
||||||
|
|
||||||
|
for (i = 0; i < HTTPD_TEST_MAX_URI_HANDLERS + 1; i++) {
|
||||||
|
sprintf(x[i],"%d",i);
|
||||||
|
uris[i] = handler_limit_uri(x[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register multiple instances of the same handler for MAX URI Handlers */
|
||||||
|
for (i = 0; i < HTTPD_TEST_MAX_URI_HANDLERS; i++) {
|
||||||
|
TEST_ASSERT(httpd_register_uri_handler(hd, &uris[i]) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register the MAX URI + 1 Handlers should fail */
|
||||||
|
TEST_ASSERT(httpd_register_uri_handler(hd, &uris[HTTPD_TEST_MAX_URI_HANDLERS]) != ESP_OK);
|
||||||
|
|
||||||
|
/* Unregister the one of the Handler should pass */
|
||||||
|
TEST_ASSERT(httpd_unregister_uri_handler(hd, uris[0].uri, uris[0].method) == ESP_OK);
|
||||||
|
|
||||||
|
/* Unregister non added Handler should fail */
|
||||||
|
TEST_ASSERT(httpd_unregister_uri_handler(hd, uris[0].uri, uris[0].method) != ESP_OK);
|
||||||
|
|
||||||
|
/* Register the MAX URI Handler should pass */
|
||||||
|
TEST_ASSERT(httpd_register_uri_handler(hd, &uris[0]) == ESP_OK);
|
||||||
|
|
||||||
|
/* Reregister same instance of handler should fail */
|
||||||
|
TEST_ASSERT(httpd_register_uri_handler(hd, &uris[0]) != ESP_OK);
|
||||||
|
|
||||||
|
/* Register the MAX URI + 1 Handlers should fail */
|
||||||
|
TEST_ASSERT(httpd_register_uri_handler(hd, &uris[HTTPD_TEST_MAX_URI_HANDLERS]) != ESP_OK);
|
||||||
|
|
||||||
|
/* Unregister the same handler for MAX URI Handlers */
|
||||||
|
for (i = 0; i < HTTPD_TEST_MAX_URI_HANDLERS; i++) {
|
||||||
|
TEST_ASSERT(httpd_unregister_uri_handler(hd, uris[i].uri, uris[i].method) == ESP_OK);
|
||||||
|
}
|
||||||
|
basic_sanity = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************* Test Handler Limit End *******************/
|
||||||
|
|
||||||
|
httpd_handle_t test_httpd_start(uint16_t id)
|
||||||
|
{
|
||||||
|
httpd_handle_t hd;
|
||||||
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
|
config.max_uri_handlers = HTTPD_TEST_MAX_URI_HANDLERS;
|
||||||
|
config.server_port += id;
|
||||||
|
config.ctrl_port += id;
|
||||||
|
TEST_ASSERT(httpd_start(&hd, &config) == ESP_OK)
|
||||||
|
return hd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SERVER_INSTANCES 2
|
||||||
|
|
||||||
|
/* Currently this only tests for the number of tasks.
|
||||||
|
* Heap leakage is not tested as LWIP allocates memory
|
||||||
|
* which may not be freed immedietly causing erroneous
|
||||||
|
* evaluation. Another test to implement would be the
|
||||||
|
* monitoring of open sockets, but LWIP presently provides
|
||||||
|
* no such API for getting the number of open sockets.
|
||||||
|
*/
|
||||||
|
TEST_CASE("Leak Test", "[HTTP SERVER]")
|
||||||
|
{
|
||||||
|
httpd_handle_t hd[SERVER_INSTANCES];
|
||||||
|
unsigned task_count;
|
||||||
|
bool res = true;
|
||||||
|
|
||||||
|
test_case_uses_tcpip();
|
||||||
|
|
||||||
|
task_count = uxTaskGetNumberOfTasks();
|
||||||
|
printf("Initial task count: %d\n", task_count);
|
||||||
|
|
||||||
|
pre_start_mem = esp_get_free_heap_size();
|
||||||
|
|
||||||
|
for (int i = 0; i < SERVER_INSTANCES; i++) {
|
||||||
|
hd[i] = test_httpd_start(i);
|
||||||
|
vTaskDelay(10);
|
||||||
|
unsigned num_tasks = uxTaskGetNumberOfTasks();
|
||||||
|
task_count++;
|
||||||
|
if (num_tasks != task_count) {
|
||||||
|
printf("Incorrect task count (starting): %d expected %d\n",
|
||||||
|
num_tasks, task_count);
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < SERVER_INSTANCES; i++) {
|
||||||
|
if (httpd_stop(hd[i]) != ESP_OK) {
|
||||||
|
printf("Failed to stop httpd task %d\n", i);
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
vTaskDelay(10);
|
||||||
|
unsigned num_tasks = uxTaskGetNumberOfTasks();
|
||||||
|
task_count--;
|
||||||
|
if (num_tasks != task_count) {
|
||||||
|
printf("Incorrect task count (stopping): %d expected %d\n",
|
||||||
|
num_tasks, task_count);
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post_stop_mem = esp_get_free_heap_size();
|
||||||
|
TEST_ASSERT(res == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Basic Functionality Tests", "[HTTP SERVER]")
|
||||||
|
{
|
||||||
|
httpd_handle_t hd;
|
||||||
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
|
|
||||||
|
test_case_uses_tcpip();
|
||||||
|
|
||||||
|
TEST_ASSERT(httpd_start(&hd, &config) == ESP_OK);
|
||||||
|
test_handler_limit(hd);
|
||||||
|
TEST_ASSERT(httpd_stop(hd) == ESP_OK);
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||||
|
# in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(tests)
|
||||||
|
|
||||||
|
target_include_directories(tests.elf PRIVATE main/include)
|
9
examples/protocols/http_server/advanced_tests/Makefile
Normal file
9
examples/protocols/http_server/advanced_tests/Makefile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||||
|
# project subdirectory.
|
||||||
|
#
|
||||||
|
|
||||||
|
PROJECT_NAME := tests
|
||||||
|
|
||||||
|
include $(IDF_PATH)/make/project.mk
|
||||||
|
|
@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import imp
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
|
|
||||||
|
# This environment variable is expected on the host machine
|
||||||
|
test_fw_path = os.getenv("TEST_FW_PATH")
|
||||||
|
if test_fw_path and test_fw_path not in sys.path:
|
||||||
|
sys.path.insert(0, test_fw_path)
|
||||||
|
|
||||||
|
# When running on local machine execute the following before running this script
|
||||||
|
# > make app bootloader
|
||||||
|
# > make print_flash_cmd | tail -n 1 > build/download.config
|
||||||
|
# > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
|
||||||
|
|
||||||
|
import TinyFW
|
||||||
|
import IDF
|
||||||
|
import Utility
|
||||||
|
|
||||||
|
# Import client module
|
||||||
|
expath = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
client = imp.load_source("client", expath + "/scripts/test.py")
|
||||||
|
|
||||||
|
# Due to connectivity issues (between runner host and DUT) in the runner environment,
|
||||||
|
# some of the `advanced_tests` are ignored. These tests are intended for verifying
|
||||||
|
# the expected limits of the http_server capabilities, and implement sending and receiving
|
||||||
|
# of large HTTP packets and malformed requests, running multiple parallel sessions, etc.
|
||||||
|
# It is advised that all these tests be run locally, when making changes or adding new
|
||||||
|
# features to this component.
|
||||||
|
@IDF.idf_example_test(env_tag="Example_WIFI")
|
||||||
|
def test_examples_protocol_http_server_advanced(env, extra_data):
|
||||||
|
# Acquire DUT
|
||||||
|
dut1 = env.get_dut("http_server", "examples/protocols/http_server/advanced_tests")
|
||||||
|
|
||||||
|
# Get binary file
|
||||||
|
binary_file = os.path.join(dut1.app.binary_path, "tests.bin")
|
||||||
|
bin_size = os.path.getsize(binary_file)
|
||||||
|
IDF.log_performance("http_server_bin_size", "{}KB".format(bin_size//1024))
|
||||||
|
IDF.check_performance("http_server_bin_size", bin_size//1024)
|
||||||
|
|
||||||
|
# Upload binary and start testing
|
||||||
|
Utility.console_log("Starting http_server advanced test app")
|
||||||
|
dut1.start_app()
|
||||||
|
|
||||||
|
# Parse IP address of STA
|
||||||
|
Utility.console_log("Waiting to connect with AP")
|
||||||
|
got_ip = dut1.expect(re.compile(r"(?:[\s\S]*)Got IP: '(\d+.\d+.\d+.\d+)'"), timeout=30)[0]
|
||||||
|
|
||||||
|
got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Started HTTP server on port: '(\d+)'"), timeout=15)[0]
|
||||||
|
result = dut1.expect(re.compile(r"(?:[\s\S]*)Max URI handlers: '(\d+)'(?:[\s\S]*)Max Open Sessions: '(\d+)'(?:[\s\S]*)Max Header Length: '(\d+)'(?:[\s\S]*)Max URI Length: '(\d+)'(?:[\s\S]*)Max Stack Size: '(\d+)'"), timeout=15)
|
||||||
|
max_uri_handlers = int(result[0])
|
||||||
|
max_sessions = int(result[1])
|
||||||
|
max_hdr_len = int(result[2])
|
||||||
|
max_uri_len = int(result[3])
|
||||||
|
max_stack_size = int(result[4])
|
||||||
|
|
||||||
|
Utility.console_log("Got IP : " + got_ip)
|
||||||
|
Utility.console_log("Got Port : " + got_port)
|
||||||
|
|
||||||
|
# Run test script
|
||||||
|
# If failed raise appropriate exception
|
||||||
|
failed = False
|
||||||
|
|
||||||
|
Utility.console_log("Sessions and Context Tests...")
|
||||||
|
if not client.spillover_session(got_ip, got_port, max_sessions):
|
||||||
|
Utility.console_log("Ignoring failure")
|
||||||
|
if not client.parallel_sessions_adder(got_ip, got_port, max_sessions):
|
||||||
|
Utility.console_log("Ignoring failure")
|
||||||
|
if not client.leftover_data_test(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.async_response_test(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.recv_timeout_test(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
|
||||||
|
## This test fails a lot! Enable when connection is stable
|
||||||
|
#test_size = 50*1024 # 50KB
|
||||||
|
#if not client.packet_size_limit_test(got_ip, got_port, test_size):
|
||||||
|
# Utility.console_log("Ignoring failure")
|
||||||
|
|
||||||
|
Utility.console_log("Getting initial stack usage...")
|
||||||
|
if not client.get_hello(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
|
||||||
|
inital_stack = int(dut1.expect(re.compile(r"(?:[\s\S]*)Free Stack for server task: '(\d+)'"), timeout=15)[0])
|
||||||
|
|
||||||
|
if inital_stack < 0.1*max_stack_size:
|
||||||
|
Utility.console_log("More than 90% of stack being used on server start")
|
||||||
|
failed = True
|
||||||
|
|
||||||
|
Utility.console_log("Basic HTTP Client Tests...")
|
||||||
|
if not client.get_hello(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.post_hello(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.put_hello(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.post_echo(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.get_echo(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.put_echo(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.get_hello_type(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.get_hello_status(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.get_false_uri(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
|
||||||
|
Utility.console_log("Error code tests...")
|
||||||
|
if not client.code_500_server_error_test(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.code_501_method_not_impl(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.code_505_version_not_supported(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.code_400_bad_request(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.code_404_not_found(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.code_405_method_not_allowed(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.code_408_req_timeout(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
if not client.code_414_uri_too_long(got_ip, got_port, max_uri_len):
|
||||||
|
Utility.console_log("Ignoring failure")
|
||||||
|
if not client.code_431_hdr_too_long(got_ip, got_port, max_hdr_len):
|
||||||
|
Utility.console_log("Ignoring failure")
|
||||||
|
if not client.test_upgrade_not_supported(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
|
||||||
|
Utility.console_log("Getting final stack usage...")
|
||||||
|
if not client.get_hello(got_ip, got_port):
|
||||||
|
failed = True
|
||||||
|
|
||||||
|
final_stack = int(dut1.expect(re.compile(r"(?:[\s\S]*)Free Stack for server task: '(\d+)'"), timeout=15)[0])
|
||||||
|
|
||||||
|
if final_stack < 0.05*max_stack_size:
|
||||||
|
Utility.console_log("More than 95% of stack got used during tests")
|
||||||
|
failed = True
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_examples_protocol_http_server_advanced()
|
@ -0,0 +1,5 @@
|
|||||||
|
set(COMPONENT_SRCS "main.c"
|
||||||
|
"tests.c")
|
||||||
|
set(COMPONENT_ADD_INCLUDEDIRS ". include")
|
||||||
|
|
||||||
|
register_component()
|
@ -0,0 +1,16 @@
|
|||||||
|
menu "Example Configuration"
|
||||||
|
|
||||||
|
config WIFI_SSID
|
||||||
|
string "WiFi SSID"
|
||||||
|
default "myssid"
|
||||||
|
help
|
||||||
|
SSID (network name) for the example to connect to.
|
||||||
|
|
||||||
|
config WIFI_PASSWORD
|
||||||
|
string "WiFi Password"
|
||||||
|
default "mypassword"
|
||||||
|
help
|
||||||
|
WiFi password (WPA or WPA2) for the example to use.
|
||||||
|
Can be left blank if the network has no security set.
|
||||||
|
|
||||||
|
endmenu
|
@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
# "main" pseudo-component makefile.
|
||||||
|
#
|
||||||
|
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
#ifndef __HTTPD_TESTS_H__
|
||||||
|
#define __HTTPD_TESTS_H__
|
||||||
|
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
|
||||||
|
extern httpd_handle_t start_tests(void);
|
||||||
|
extern void stop_tests(httpd_handle_t hd);
|
||||||
|
|
||||||
|
#endif // __HTTPD_TESTS_H__
|
81
examples/protocols/http_server/advanced_tests/main/main.c
Normal file
81
examples/protocols/http_server/advanced_tests/main/main.c
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "esp_event_loop.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
|
||||||
|
#include "tests.h"
|
||||||
|
|
||||||
|
/* The examples use simple WiFi configuration that you can set via
|
||||||
|
'make menuconfig'.
|
||||||
|
|
||||||
|
If you'd rather not, just change the below entries to strings with
|
||||||
|
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
|
||||||
|
*/
|
||||||
|
#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
|
||||||
|
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
|
||||||
|
|
||||||
|
static const char *TAG="TEST_WIFI";
|
||||||
|
|
||||||
|
static esp_err_t event_handler(void *ctx, system_event_t *event)
|
||||||
|
{
|
||||||
|
httpd_handle_t *hd = (httpd_handle_t *) ctx;
|
||||||
|
|
||||||
|
switch(event->event_id) {
|
||||||
|
case SYSTEM_EVENT_STA_START:
|
||||||
|
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_connect());
|
||||||
|
break;
|
||||||
|
case SYSTEM_EVENT_STA_GOT_IP:
|
||||||
|
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
|
||||||
|
ESP_LOGI(TAG, "Got IP: '%s'",
|
||||||
|
ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
|
||||||
|
|
||||||
|
// Start webserver tests
|
||||||
|
if (*hd == NULL) {
|
||||||
|
*hd = start_tests();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||||
|
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_connect());
|
||||||
|
|
||||||
|
// Stop webserver tests
|
||||||
|
if (*hd) {
|
||||||
|
stop_tests(*hd);
|
||||||
|
*hd = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialise_wifi(void)
|
||||||
|
{
|
||||||
|
tcpip_adapter_init();
|
||||||
|
static httpd_handle_t hd = NULL;
|
||||||
|
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, &hd));
|
||||||
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
||||||
|
wifi_config_t wifi_config = {
|
||||||
|
.sta = {
|
||||||
|
.ssid = EXAMPLE_WIFI_SSID,
|
||||||
|
.password = EXAMPLE_WIFI_PASS,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_start());
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main()
|
||||||
|
{
|
||||||
|
ESP_ERROR_CHECK(nvs_flash_init());
|
||||||
|
initialise_wifi();
|
||||||
|
}
|
310
examples/protocols/http_server/advanced_tests/main/tests.c
Normal file
310
examples/protocols/http_server/advanced_tests/main/tests.c
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
|
||||||
|
#include "tests.h"
|
||||||
|
|
||||||
|
static const char *TAG="TESTS";
|
||||||
|
|
||||||
|
int pre_start_mem, post_stop_mem, post_stop_min_mem;
|
||||||
|
bool basic_sanity = true;
|
||||||
|
|
||||||
|
struct async_resp_arg {
|
||||||
|
httpd_handle_t hd;
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
/********************* Basic Handlers Start *******************/
|
||||||
|
|
||||||
|
esp_err_t hello_get_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#define STR "Hello World!"
|
||||||
|
ESP_LOGI(TAG, "Free Stack for server task: '%d'", uxTaskGetStackHighWaterMark(NULL));
|
||||||
|
httpd_resp_send(req, STR, strlen(STR));
|
||||||
|
return ESP_OK;
|
||||||
|
#undef STR
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t hello_type_get_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#define STR "Hello World!"
|
||||||
|
httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
|
||||||
|
httpd_resp_send(req, STR, strlen(STR));
|
||||||
|
return ESP_OK;
|
||||||
|
#undef STR
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t hello_status_get_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#define STR "Hello World!"
|
||||||
|
httpd_resp_set_status(req, HTTPD_500);
|
||||||
|
httpd_resp_send(req, STR, strlen(STR));
|
||||||
|
return ESP_OK;
|
||||||
|
#undef STR
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t echo_post_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "/echo handler read content length %d", req->content_len);
|
||||||
|
|
||||||
|
char* buf = malloc(req->content_len + 1);
|
||||||
|
size_t off = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!buf) {
|
||||||
|
httpd_resp_send_500(req);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (off < req->content_len) {
|
||||||
|
/* Read data received in the request */
|
||||||
|
ret = httpd_req_recv(req, buf + off, req->content_len - off);
|
||||||
|
if (ret <= 0) {
|
||||||
|
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||||
|
httpd_resp_send_408(req);
|
||||||
|
}
|
||||||
|
free (buf);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
off += ret;
|
||||||
|
ESP_LOGI(TAG, "/echo handler recv length %d", ret);
|
||||||
|
}
|
||||||
|
buf[off] = '\0';
|
||||||
|
|
||||||
|
if (req->content_len < 128) {
|
||||||
|
ESP_LOGI(TAG, "/echo handler read %s", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search for Custom header field */
|
||||||
|
char* req_hdr = 0;
|
||||||
|
size_t hdr_len = httpd_req_get_hdr_value_len(req, "Custom");
|
||||||
|
if (hdr_len) {
|
||||||
|
/* Read Custom header value */
|
||||||
|
req_hdr = malloc(hdr_len + 1);
|
||||||
|
if (req_hdr) {
|
||||||
|
httpd_req_get_hdr_value_str(req, "Custom", req_hdr, hdr_len + 1);
|
||||||
|
|
||||||
|
/* Set as additional header for response packet */
|
||||||
|
httpd_resp_set_hdr(req, "Custom", req_hdr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
httpd_resp_send(req, buf, req->content_len);
|
||||||
|
free (req_hdr);
|
||||||
|
free (buf);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void adder_free_func(void *ctx)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Custom Free Context function called");
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a context, keep incrementing value in the context, by whatever was
|
||||||
|
* received. Return the result
|
||||||
|
*/
|
||||||
|
esp_err_t adder_post_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
char buf[10];
|
||||||
|
char outbuf[50];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Read data received in the request */
|
||||||
|
ret = httpd_req_recv(req, buf, sizeof(buf));
|
||||||
|
if (ret <= 0) {
|
||||||
|
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||||
|
httpd_resp_send_408(req);
|
||||||
|
}
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[ret] = '\0';
|
||||||
|
int val = atoi(buf);
|
||||||
|
ESP_LOGI(TAG, "/adder handler read %d", val);
|
||||||
|
|
||||||
|
if (! req->sess_ctx) {
|
||||||
|
ESP_LOGI(TAG, "/adder allocating new session");
|
||||||
|
req->sess_ctx = malloc(sizeof(int));
|
||||||
|
req->free_ctx = adder_free_func;
|
||||||
|
*(int *)req->sess_ctx = 0;
|
||||||
|
}
|
||||||
|
int *adder = (int *)req->sess_ctx;
|
||||||
|
*adder += val;
|
||||||
|
|
||||||
|
snprintf(outbuf, sizeof(outbuf),"%d", *adder);
|
||||||
|
httpd_resp_send(req, outbuf, strlen(outbuf));
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t leftover_data_post_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
/* Only echo the first 10 bytes of the request, leaving the rest of the
|
||||||
|
* request data as is.
|
||||||
|
*/
|
||||||
|
char buf[11];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Read data received in the request */
|
||||||
|
ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
|
if (ret <= 0) {
|
||||||
|
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||||
|
httpd_resp_send_408(req);
|
||||||
|
}
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[ret] = '\0';
|
||||||
|
ESP_LOGI(TAG, "leftover data handler read %s", buf);
|
||||||
|
httpd_resp_send(req, buf, strlen(buf));
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int httpd_default_send(httpd_handle_t hd, int sockfd, const char *buf, unsigned buf_len, int flags);
|
||||||
|
void generate_async_resp(void *arg)
|
||||||
|
{
|
||||||
|
char buf[250];
|
||||||
|
struct async_resp_arg *resp_arg = (struct async_resp_arg *)arg;
|
||||||
|
httpd_handle_t hd = resp_arg->hd;
|
||||||
|
int fd = resp_arg->fd;
|
||||||
|
#define HTTPD_HDR_STR "HTTP/1.1 200 OK\r\n" \
|
||||||
|
"Content-Type: text/html\r\n" \
|
||||||
|
"Content-Length: %d\r\n"
|
||||||
|
#define STR "Hello Double World!"
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Executing queued work fd : %d", fd);
|
||||||
|
|
||||||
|
snprintf(buf, sizeof(buf), HTTPD_HDR_STR,
|
||||||
|
strlen(STR));
|
||||||
|
httpd_default_send(hd, fd, buf, strlen(buf), 0);
|
||||||
|
/* Space for sending additional headers based on set_header */
|
||||||
|
httpd_default_send(hd, fd, "\r\n", strlen("\r\n"), 0);
|
||||||
|
httpd_default_send(hd, fd, STR, strlen(STR), 0);
|
||||||
|
#undef STR
|
||||||
|
free(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t async_get_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#define STR "Hello World!"
|
||||||
|
httpd_resp_send(req, STR, strlen(STR));
|
||||||
|
/* Also register a HTTPD Work which sends the same data on the same
|
||||||
|
* socket again
|
||||||
|
*/
|
||||||
|
struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
|
||||||
|
resp_arg->hd = req->handle;
|
||||||
|
resp_arg->fd = httpd_req_to_sockfd(req);
|
||||||
|
if (resp_arg->fd < 0) {
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Queuing work fd : %d", resp_arg->fd);
|
||||||
|
httpd_queue_work(req->handle, generate_async_resp, resp_arg);
|
||||||
|
return ESP_OK;
|
||||||
|
#undef STR
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
httpd_uri_t basic_handlers[] = {
|
||||||
|
{ .uri = "/hello/type_html",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = hello_type_get_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
},
|
||||||
|
{ .uri = "/hello",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = hello_get_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
},
|
||||||
|
{ .uri = "/hello/status_500",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = hello_status_get_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
},
|
||||||
|
{ .uri = "/echo",
|
||||||
|
.method = HTTP_POST,
|
||||||
|
.handler = echo_post_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
},
|
||||||
|
{ .uri = "/echo",
|
||||||
|
.method = HTTP_PUT,
|
||||||
|
.handler = echo_post_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
},
|
||||||
|
{ .uri = "/leftover_data",
|
||||||
|
.method = HTTP_POST,
|
||||||
|
.handler = leftover_data_post_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
},
|
||||||
|
{ .uri = "/adder",
|
||||||
|
.method = HTTP_POST,
|
||||||
|
.handler = adder_post_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
},
|
||||||
|
{ .uri = "/async_data",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = async_get_handler,
|
||||||
|
.user_ctx = NULL,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int basic_handlers_no = sizeof(basic_handlers)/sizeof(httpd_uri_t);
|
||||||
|
void register_basic_handlers(httpd_handle_t hd)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
ESP_LOGI(TAG, "Registering basic handlers");
|
||||||
|
ESP_LOGI(TAG, "No of handlers = %d", basic_handlers_no);
|
||||||
|
for (i = 0; i < basic_handlers_no; i++) {
|
||||||
|
if (httpd_register_uri_handler(hd, &basic_handlers[i]) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "register uri failed for %d", i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Success");
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_handle_t test_httpd_start()
|
||||||
|
{
|
||||||
|
pre_start_mem = esp_get_free_heap_size();
|
||||||
|
httpd_handle_t hd;
|
||||||
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
|
config.server_port = 1234;
|
||||||
|
|
||||||
|
/* This check should be a part of http_server */
|
||||||
|
config.max_open_sockets = (CONFIG_LWIP_MAX_SOCKETS - 3);
|
||||||
|
|
||||||
|
if (httpd_start(&hd, &config) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Started HTTP server on port: '%d'", config.server_port);
|
||||||
|
ESP_LOGI(TAG, "Max URI handlers: '%d'", config.max_uri_handlers);
|
||||||
|
ESP_LOGI(TAG, "Max Open Sessions: '%d'", config.max_open_sockets);
|
||||||
|
ESP_LOGI(TAG, "Max Header Length: '%d'", HTTPD_MAX_REQ_HDR_LEN);
|
||||||
|
ESP_LOGI(TAG, "Max URI Length: '%d'", HTTPD_MAX_URI_LEN);
|
||||||
|
ESP_LOGI(TAG, "Max Stack Size: '%d'", config.stack_size);
|
||||||
|
return hd;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_httpd_stop(httpd_handle_t hd)
|
||||||
|
{
|
||||||
|
httpd_stop(hd);
|
||||||
|
post_stop_mem = esp_get_free_heap_size();
|
||||||
|
ESP_LOGI(TAG, "HTTPD Stop: Current free memory: %d", post_stop_mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_handle_t start_tests()
|
||||||
|
{
|
||||||
|
httpd_handle_t hd = test_httpd_start();
|
||||||
|
if (hd) {
|
||||||
|
register_basic_handlers(hd);
|
||||||
|
}
|
||||||
|
return hd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop_tests(httpd_handle_t hd)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Stopping httpd");
|
||||||
|
test_httpd_stop(hd);
|
||||||
|
}
|
863
examples/protocols/http_server/advanced_tests/scripts/test.py
Normal file
863
examples/protocols/http_server/advanced_tests/scripts/test.py
Normal file
@ -0,0 +1,863 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# Utility for testing the web server. Test cases:
|
||||||
|
# Assume the device supports 'n' simultaneous open sockets
|
||||||
|
#
|
||||||
|
# HTTP Server Tests
|
||||||
|
#
|
||||||
|
# 0. Firmware Settings:
|
||||||
|
# - Create a dormant thread whose sole job is to call httpd_stop() when instructed
|
||||||
|
# - Measure the following before httpd_start() is called:
|
||||||
|
# - current free memory
|
||||||
|
# - current free sockets
|
||||||
|
# - Measure the same whenever httpd_stop is called
|
||||||
|
# - Register maximum possible URI handlers: should be successful
|
||||||
|
# - Register one more URI handler: should fail
|
||||||
|
# - Deregister on URI handler: should be successful
|
||||||
|
# - Register on more URI handler: should succeed
|
||||||
|
# - Register separate handlers for /hello, /hello/type_html. Also
|
||||||
|
# ensure that /hello/type_html is registered BEFORE /hello. (tests
|
||||||
|
# that largest matching URI is picked properly)
|
||||||
|
# - Create URI handler /adder. Make sure it uses a custom free_ctx
|
||||||
|
# structure to free it up
|
||||||
|
|
||||||
|
# 1. Using Standard Python HTTP Client
|
||||||
|
# - simple GET on /hello (returns Hello World. Ensures that basic
|
||||||
|
# firmware tests are complete, or returns error)
|
||||||
|
# - POST on /hello (should fail)
|
||||||
|
# - PUT on /hello (should fail)
|
||||||
|
# - simple POST on /echo (returns whatever the POST data)
|
||||||
|
# - simple PUT on /echo (returns whatever the PUT data)
|
||||||
|
# - GET on /echo (should fail)
|
||||||
|
# - simple GET on /hello/type_html (returns Content type as text/html)
|
||||||
|
# - simple GET on /hello/status_500 (returns HTTP status 500)
|
||||||
|
# - simple GET on /false_uri (returns HTTP status 404)
|
||||||
|
# - largest matching URI handler is picked is already verified because
|
||||||
|
# of /hello and /hello/type_html tests
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# 2. Session Tests
|
||||||
|
# - Sessions + Pipelining basics:
|
||||||
|
# - Create max supported sessions
|
||||||
|
# - On session i,
|
||||||
|
# - send 3 back-to-back POST requests with data i on /adder
|
||||||
|
# - read back 3 responses. They should be i, 2i and 3i
|
||||||
|
# - Tests that
|
||||||
|
# - pipelining works
|
||||||
|
# - per-session context is maintained for all supported
|
||||||
|
# sessions
|
||||||
|
# - Close all sessions
|
||||||
|
#
|
||||||
|
# - Cleanup leftover data: Tests that the web server properly cleans
|
||||||
|
# up leftover data
|
||||||
|
# - Create a session
|
||||||
|
# - POST on /leftover_data with 52 bytes of data (data includes
|
||||||
|
# \r\n)(the handler only
|
||||||
|
# reads first 10 bytes and returns them, leaving the rest of the
|
||||||
|
# bytes unread)
|
||||||
|
# - GET on /hello (should return 'Hello World')
|
||||||
|
# - POST on /false_uri with 52 bytes of data (data includes \r\n)
|
||||||
|
# (should return HTTP 404)
|
||||||
|
# - GET on /hello (should return 'Hello World')
|
||||||
|
#
|
||||||
|
# - Test HTTPd Asynchronous response
|
||||||
|
# - Create a session
|
||||||
|
# - GET on /async_data
|
||||||
|
# - returns 'Hello World!' as a response
|
||||||
|
# - the handler schedules an async response, which generates a second
|
||||||
|
# response 'Hello Double World!'
|
||||||
|
#
|
||||||
|
# - Spillover test
|
||||||
|
# - Create max supported sessions with the web server
|
||||||
|
# - GET /hello on all the sessions (should return Hello World)
|
||||||
|
# - Create one more session, this should fail
|
||||||
|
# - GET /hello on all the sessions (should return Hello World)
|
||||||
|
#
|
||||||
|
# - Timeout test
|
||||||
|
# - Create a session and only Send 'GE' on the same (simulates a
|
||||||
|
# client that left the network halfway through a request)
|
||||||
|
# - Wait for recv-wait-timeout
|
||||||
|
# - Server should automatically close the socket
|
||||||
|
|
||||||
|
|
||||||
|
############# TODO TESTS #############
|
||||||
|
|
||||||
|
# 3. Stress Tests
|
||||||
|
#
|
||||||
|
# - httperf
|
||||||
|
# - Run the following httperf command:
|
||||||
|
# httperf --server=10.31.130.126 --wsess=8,50,0.5 --rate 8 --burst-length 2
|
||||||
|
#
|
||||||
|
# - The above implies that the test suite will open
|
||||||
|
# - 8 simultaneous connections with the server
|
||||||
|
# - the rate of opening the sessions will be 8 per sec. So in our
|
||||||
|
# case, a new connection will be opened every 0.2 seconds for 1 second
|
||||||
|
# - The burst length 2 indicates that 2 requests will be sent
|
||||||
|
# simultaneously on the same connection in a single go
|
||||||
|
# - 0.5 seconds is the time between sending out 2 bursts
|
||||||
|
# - 50 is the total number of requests that will be sent out
|
||||||
|
#
|
||||||
|
# - So in the above example, the test suite will open 8
|
||||||
|
# connections, each separated by 0.2 seconds. On each connection
|
||||||
|
# it will send 2 requests in a single burst. The bursts on a
|
||||||
|
# single connection will be separated by 0.5 seconds. A total of
|
||||||
|
# 25 bursts (25 x 2 = 50) will be sent out
|
||||||
|
|
||||||
|
# 4. Leak Tests
|
||||||
|
# - Simple Leak test
|
||||||
|
# - Simple GET on /hello/restart (returns success, stop web server, measures leaks, restarts webserver)
|
||||||
|
# - Simple GET on /hello/restart_results (returns the leak results)
|
||||||
|
# - Leak test with open sockets
|
||||||
|
# - Open 8 sessions
|
||||||
|
# - Simple GET on /hello/restart (returns success, stop web server,
|
||||||
|
# measures leaks, restarts webserver)
|
||||||
|
# - All sockets should get closed
|
||||||
|
# - Simple GET on /hello/restart_results (returns the leak results)
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from future import standard_library
|
||||||
|
standard_library.install_aliases()
|
||||||
|
from builtins import str
|
||||||
|
from builtins import range
|
||||||
|
from builtins import object
|
||||||
|
import threading
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
import http.client
|
||||||
|
import sys
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
import Utility
|
||||||
|
|
||||||
|
_verbose_ = False
|
||||||
|
|
||||||
|
class Session(object):
|
||||||
|
def __init__(self, addr, port, timeout = 15):
|
||||||
|
self.client = socket.create_connection((addr, int(port)), timeout = timeout)
|
||||||
|
self.target = addr
|
||||||
|
self.status = 0
|
||||||
|
self.encoding = ''
|
||||||
|
self.content_type = ''
|
||||||
|
self.content_len = 0
|
||||||
|
|
||||||
|
def send_err_check(self, request, data=None):
|
||||||
|
rval = True
|
||||||
|
try:
|
||||||
|
self.client.sendall(request.encode());
|
||||||
|
if data:
|
||||||
|
self.client.sendall(data.encode())
|
||||||
|
except socket.error as err:
|
||||||
|
self.client.close()
|
||||||
|
Utility.console_log("Socket Error in send :", err)
|
||||||
|
rval = False
|
||||||
|
return rval
|
||||||
|
|
||||||
|
def send_get(self, path, headers=None):
|
||||||
|
request = "GET " + path + " HTTP/1.1\r\nHost: " + self.target
|
||||||
|
if headers:
|
||||||
|
for field, value in headers.items():
|
||||||
|
request += "\r\n"+field+": "+value
|
||||||
|
request += "\r\n\r\n"
|
||||||
|
return self.send_err_check(request)
|
||||||
|
|
||||||
|
def send_put(self, path, data, headers=None):
|
||||||
|
request = "PUT " + path + " HTTP/1.1\r\nHost: " + self.target
|
||||||
|
if headers:
|
||||||
|
for field, value in headers.items():
|
||||||
|
request += "\r\n"+field+": "+value
|
||||||
|
request += "\r\nContent-Length: " + str(len(data)) +"\r\n\r\n"
|
||||||
|
return self.send_err_check(request, data)
|
||||||
|
|
||||||
|
def send_post(self, path, data, headers=None):
|
||||||
|
request = "POST " + path + " HTTP/1.1\r\nHost: " + self.target
|
||||||
|
if headers:
|
||||||
|
for field, value in headers.items():
|
||||||
|
request += "\r\n"+field+": "+value
|
||||||
|
request += "\r\nContent-Length: " + str(len(data)) +"\r\n\r\n"
|
||||||
|
return self.send_err_check(request, data)
|
||||||
|
|
||||||
|
def read_resp_hdrs(self):
|
||||||
|
try:
|
||||||
|
state = 'nothing'
|
||||||
|
resp_read = ''
|
||||||
|
while True:
|
||||||
|
char = self.client.recv(1).decode()
|
||||||
|
if char == '\r' and state == 'nothing':
|
||||||
|
state = 'first_cr'
|
||||||
|
elif char == '\n' and state == 'first_cr':
|
||||||
|
state = 'first_lf'
|
||||||
|
elif char == '\r' and state == 'first_lf':
|
||||||
|
state = 'second_cr'
|
||||||
|
elif char == '\n' and state == 'second_cr':
|
||||||
|
state = 'second_lf'
|
||||||
|
else:
|
||||||
|
state = 'nothing'
|
||||||
|
resp_read += char
|
||||||
|
if state == 'second_lf':
|
||||||
|
break
|
||||||
|
# Handle first line
|
||||||
|
line_hdrs = resp_read.splitlines()
|
||||||
|
line_comp = line_hdrs[0].split()
|
||||||
|
self.status = line_comp[1]
|
||||||
|
del line_hdrs[0]
|
||||||
|
self.encoding = ''
|
||||||
|
self.content_type = ''
|
||||||
|
headers = dict()
|
||||||
|
# Process other headers
|
||||||
|
for h in range(len(line_hdrs)):
|
||||||
|
line_comp = line_hdrs[h].split(':')
|
||||||
|
if line_comp[0] == 'Content-Length':
|
||||||
|
self.content_len = int(line_comp[1])
|
||||||
|
if line_comp[0] == 'Content-Type':
|
||||||
|
self.content_type = line_comp[1].lstrip()
|
||||||
|
if line_comp[0] == 'Transfer-Encoding':
|
||||||
|
self.encoding = line_comp[1].lstrip()
|
||||||
|
if len(line_comp) == 2:
|
||||||
|
headers[line_comp[0]] = line_comp[1].lstrip()
|
||||||
|
return headers
|
||||||
|
except socket.error as err:
|
||||||
|
self.client.close()
|
||||||
|
Utility.console_log("Socket Error in recv :", err)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def read_resp_data(self):
|
||||||
|
try:
|
||||||
|
read_data = ''
|
||||||
|
if self.encoding != 'chunked':
|
||||||
|
while len(read_data) != self.content_len:
|
||||||
|
read_data += self.client.recv(self.content_len).decode()
|
||||||
|
else:
|
||||||
|
chunk_data_buf = ''
|
||||||
|
while (True):
|
||||||
|
# Read one character into temp buffer
|
||||||
|
read_ch = self.client.recv(1)
|
||||||
|
# Check CRLF
|
||||||
|
if (read_ch == '\r'):
|
||||||
|
read_ch = self.client.recv(1).decode()
|
||||||
|
if (read_ch == '\n'):
|
||||||
|
# If CRLF decode length of chunk
|
||||||
|
chunk_len = int(chunk_data_buf, 16)
|
||||||
|
# Keep adding to contents
|
||||||
|
self.content_len += chunk_len
|
||||||
|
rem_len = chunk_len
|
||||||
|
while (rem_len):
|
||||||
|
new_data = self.client.recv(rem_len)
|
||||||
|
read_data += new_data
|
||||||
|
rem_len -= len(new_data)
|
||||||
|
chunk_data_buf = ''
|
||||||
|
# Fetch remaining CRLF
|
||||||
|
if self.client.recv(2) != "\r\n":
|
||||||
|
# Error in packet
|
||||||
|
Utility.console_log("Error in chunked data")
|
||||||
|
return None
|
||||||
|
if not chunk_len:
|
||||||
|
# If last chunk
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
chunk_data_buf += '\r'
|
||||||
|
# If not CRLF continue appending
|
||||||
|
# character to chunked data buffer
|
||||||
|
chunk_data_buf += read_ch
|
||||||
|
return read_data
|
||||||
|
except socket.error as err:
|
||||||
|
self.client.close()
|
||||||
|
Utility.console_log("Socket Error in recv :", err)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.client.close()
|
||||||
|
|
||||||
|
def test_val(text, expected, received):
|
||||||
|
if expected != received:
|
||||||
|
Utility.console_log(" Fail!")
|
||||||
|
Utility.console_log(" [reason] " + text + ":")
|
||||||
|
Utility.console_log(" expected: " + str(expected))
|
||||||
|
Utility.console_log(" received: " + str(received))
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
class adder_thread (threading.Thread):
|
||||||
|
def __init__(self, id, dut, port):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.id = id
|
||||||
|
self.dut = dut
|
||||||
|
self.depth = 3
|
||||||
|
self.session = Session(dut, port)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.response = []
|
||||||
|
|
||||||
|
# Pipeline 3 requests
|
||||||
|
if (_verbose_):
|
||||||
|
Utility.console_log(" Thread: Using adder start " + str(self.id))
|
||||||
|
|
||||||
|
for _ in range(self.depth):
|
||||||
|
self.session.send_post('/adder', str(self.id))
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
for _ in range(self.depth):
|
||||||
|
self.session.read_resp_hdrs()
|
||||||
|
self.response.append(self.session.read_resp_data())
|
||||||
|
|
||||||
|
def adder_result(self):
|
||||||
|
if len(self.response) != self.depth:
|
||||||
|
Utility.console_log("Error : missing response packets")
|
||||||
|
return False
|
||||||
|
for i in range(len(self.response)):
|
||||||
|
if not test_val("Thread" + str(self.id) + " response[" + str(i) + "]",
|
||||||
|
str(self.id * (i + 1)), str(self.response[i])):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def get_hello(dut, port):
|
||||||
|
# GET /hello should return 'Hello World!'
|
||||||
|
Utility.console_log("[test] GET /hello returns 'Hello World!' =>", end=' ')
|
||||||
|
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
|
||||||
|
conn.request("GET", "/hello")
|
||||||
|
resp = conn.getresponse()
|
||||||
|
if not test_val("status_code", 200, resp.status):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
if not test_val("data", "Hello World!", resp.read().decode()):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
if not test_val("data", "text/html", resp.getheader('Content-Type')):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def put_hello(dut, port):
|
||||||
|
# PUT /hello returns 405'
|
||||||
|
Utility.console_log("[test] PUT /hello returns 405 =>", end=' ')
|
||||||
|
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
|
||||||
|
conn.request("PUT", "/hello", "Hello")
|
||||||
|
resp = conn.getresponse()
|
||||||
|
if not test_val("status_code", 405, resp.status):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def post_hello(dut, port):
|
||||||
|
# POST /hello returns 405'
|
||||||
|
Utility.console_log("[test] POST /hello returns 404 =>", end=' ')
|
||||||
|
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
|
||||||
|
conn.request("POST", "/hello", "Hello")
|
||||||
|
resp = conn.getresponse()
|
||||||
|
if not test_val("status_code", 405, resp.status):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def post_echo(dut, port):
|
||||||
|
# POST /echo echoes data'
|
||||||
|
Utility.console_log("[test] POST /echo echoes data =>", end=' ')
|
||||||
|
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
|
||||||
|
conn.request("POST", "/echo", "Hello")
|
||||||
|
resp = conn.getresponse()
|
||||||
|
if not test_val("status_code", 200, resp.status):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
if not test_val("data", "Hello", resp.read().decode()):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def put_echo(dut, port):
|
||||||
|
# PUT /echo echoes data'
|
||||||
|
Utility.console_log("[test] PUT /echo echoes data =>", end=' ')
|
||||||
|
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
|
||||||
|
conn.request("PUT", "/echo", "Hello")
|
||||||
|
resp = conn.getresponse()
|
||||||
|
if not test_val("status_code", 200, resp.status):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
if not test_val("data", "Hello", resp.read().decode()):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_echo(dut, port):
|
||||||
|
# GET /echo returns 404'
|
||||||
|
Utility.console_log("[test] GET /echo returns 405 =>", end=' ')
|
||||||
|
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
|
||||||
|
conn.request("GET", "/echo")
|
||||||
|
resp = conn.getresponse()
|
||||||
|
if not test_val("status_code", 405, resp.status):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_hello_type(dut, port):
|
||||||
|
# GET /hello/type_html returns text/html as Content-Type'
|
||||||
|
Utility.console_log("[test] GET /hello/type_html has Content-Type of text/html =>", end=' ')
|
||||||
|
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
|
||||||
|
conn.request("GET", "/hello/type_html")
|
||||||
|
resp = conn.getresponse()
|
||||||
|
if not test_val("status_code", 200, resp.status):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
if not test_val("data", "Hello World!", resp.read().decode()):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
if not test_val("data", "text/html", resp.getheader('Content-Type')):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_hello_status(dut, port):
|
||||||
|
# GET /hello/status_500 returns status 500'
|
||||||
|
Utility.console_log("[test] GET /hello/status_500 returns status 500 =>", end=' ')
|
||||||
|
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
|
||||||
|
conn.request("GET", "/hello/status_500")
|
||||||
|
resp = conn.getresponse()
|
||||||
|
if not test_val("status_code", 500, resp.status):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_false_uri(dut, port):
|
||||||
|
# GET /false_uri returns status 404'
|
||||||
|
Utility.console_log("[test] GET /false_uri returns status 404 =>", end=' ')
|
||||||
|
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
|
||||||
|
conn.request("GET", "/false_uri")
|
||||||
|
resp = conn.getresponse()
|
||||||
|
if not test_val("status_code", 404, resp.status):
|
||||||
|
conn.close()
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def parallel_sessions_adder(dut, port, max_sessions):
|
||||||
|
# POSTs on /adder in parallel sessions
|
||||||
|
Utility.console_log("[test] POST {pipelined} on /adder in " + str(max_sessions) + " sessions =>", end=' ')
|
||||||
|
t = []
|
||||||
|
# Create all sessions
|
||||||
|
for i in range(max_sessions):
|
||||||
|
t.append(adder_thread(i, dut, port))
|
||||||
|
|
||||||
|
for i in range(len(t)):
|
||||||
|
t[i].start()
|
||||||
|
|
||||||
|
for i in range(len(t)):
|
||||||
|
t[i].join()
|
||||||
|
|
||||||
|
res = True
|
||||||
|
for i in range(len(t)):
|
||||||
|
if not test_val("Thread" + str(i) + " Failed", t[i].adder_result(), True):
|
||||||
|
res = False
|
||||||
|
t[i].close()
|
||||||
|
if (res):
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return res
|
||||||
|
|
||||||
|
def async_response_test(dut, port):
|
||||||
|
# Test that an asynchronous work is executed in the HTTPD's context
|
||||||
|
# This is tested by reading two responses over the same session
|
||||||
|
Utility.console_log("[test] Test HTTPD Work Queue (Async response) =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
|
||||||
|
s.send_get('/async_data')
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
if not test_val("First Response", "Hello World!", s.read_resp_data()):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
if not test_val("Second Response", "Hello Double World!", s.read_resp_data()):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def leftover_data_test(dut, port):
|
||||||
|
# Leftover data in POST is purged (valid and invalid URIs)
|
||||||
|
Utility.console_log("[test] Leftover data in POST is purged (valid and invalid URIs) =>", end=' ')
|
||||||
|
s = http.client.HTTPConnection(dut + ":" + port, timeout=15)
|
||||||
|
|
||||||
|
s.request("POST", url='/leftover_data', body="abcdefghijklmnopqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz")
|
||||||
|
resp = s.getresponse()
|
||||||
|
if not test_val("Partial data", "abcdefghij", resp.read().decode()):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
s.request("GET", url='/hello')
|
||||||
|
resp = s.getresponse()
|
||||||
|
if not test_val("Hello World Data", "Hello World!", resp.read().decode()):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
s.request("POST", url='/false_uri', body="abcdefghijklmnopqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz")
|
||||||
|
resp = s.getresponse()
|
||||||
|
if not test_val("False URI Status", str(404), str(resp.status)):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
resp.read()
|
||||||
|
|
||||||
|
s.request("GET", url='/hello')
|
||||||
|
resp = s.getresponse()
|
||||||
|
if not test_val("Hello World Data", "Hello World!", resp.read().decode()):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def spillover_session(dut, port, max_sess):
|
||||||
|
# Session max_sess_sessions + 1 is rejected
|
||||||
|
Utility.console_log("[test] Session max_sess_sessions (" + str(max_sess) + ") + 1 is rejected =>", end=' ')
|
||||||
|
s = []
|
||||||
|
_verbose_ = True
|
||||||
|
for i in range(max_sess + 1):
|
||||||
|
if (_verbose_):
|
||||||
|
Utility.console_log("Executing " + str(i))
|
||||||
|
try:
|
||||||
|
a = http.client.HTTPConnection(dut + ":" + port, timeout=15)
|
||||||
|
a.request("GET", url='/hello')
|
||||||
|
resp = a.getresponse()
|
||||||
|
if not test_val("Connection " + str(i), "Hello World!", resp.read().decode()):
|
||||||
|
a.close()
|
||||||
|
break
|
||||||
|
s.append(a)
|
||||||
|
except:
|
||||||
|
if (_verbose_):
|
||||||
|
Utility.console_log("Connection " + str(i) + " rejected")
|
||||||
|
a.close()
|
||||||
|
break
|
||||||
|
|
||||||
|
# Close open connections
|
||||||
|
for a in s:
|
||||||
|
a.close()
|
||||||
|
|
||||||
|
# Check if number of connections is equal to max_sess
|
||||||
|
Utility.console_log(["Fail","Success"][len(s) == max_sess])
|
||||||
|
return (len(s) == max_sess)
|
||||||
|
|
||||||
|
def recv_timeout_test(dut, port):
|
||||||
|
Utility.console_log("[test] Timeout occurs if partial packet sent =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
s.client.sendall(b"GE")
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
if not test_val("Request Timeout", "Server closed this connection", resp):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def packet_size_limit_test(dut, port, test_size):
|
||||||
|
Utility.console_log("[test] send size limit test =>", end=' ')
|
||||||
|
retry = 5
|
||||||
|
while (retry):
|
||||||
|
retry -= 1
|
||||||
|
Utility.console_log("data size = ", test_size)
|
||||||
|
s = http.client.HTTPConnection(dut + ":" + port, timeout=15)
|
||||||
|
random_data = ''.join(string.printable[random.randint(0,len(string.printable))-1] for _ in list(range(test_size)))
|
||||||
|
path = "/echo"
|
||||||
|
s.request("POST", url=path, body=random_data)
|
||||||
|
resp = s.getresponse()
|
||||||
|
if not test_val("Error", "200", str(resp.status)):
|
||||||
|
if test_val("Error", "500", str(resp.status)):
|
||||||
|
Utility.console_log("Data too large to be allocated")
|
||||||
|
test_size = test_size//10
|
||||||
|
else:
|
||||||
|
Utility.console_log("Unexpected error")
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Retry...")
|
||||||
|
continue
|
||||||
|
resp = resp.read().decode()
|
||||||
|
result = (resp == random_data)
|
||||||
|
if not result:
|
||||||
|
test_val("Data size", str(len(random_data)), str(len(resp)))
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Retry...")
|
||||||
|
continue
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
Utility.console_log("Failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def code_500_server_error_test(dut, port):
|
||||||
|
Utility.console_log("[test] 500 Server Error test =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
# Sending a very large content length will cause malloc to fail
|
||||||
|
content_len = 2**31
|
||||||
|
s.client.sendall(("POST /echo HTTP/1.1\r\nHost: " + dut + "\r\nContent-Length: " + str(content_len) + "\r\n\r\nABCD").encode())
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
if not test_val("Server Error", "500", s.status):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def code_501_method_not_impl(dut, port):
|
||||||
|
Utility.console_log("[test] 501 Method Not Implemented =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
path = "/hello"
|
||||||
|
s.client.sendall(("ABC " + path + " HTTP/1.1\r\nHost: " + dut + "\r\n\r\n").encode())
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
# Presently server sends back 400 Bad Request
|
||||||
|
#if not test_val("Server Error", "501", s.status):
|
||||||
|
#s.close()
|
||||||
|
#return False
|
||||||
|
if not test_val("Server Error", "400", s.status):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def code_505_version_not_supported(dut, port):
|
||||||
|
Utility.console_log("[test] 505 Version Not Supported =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
path = "/hello"
|
||||||
|
s.client.sendall(("GET " + path + " HTTP/2.0\r\nHost: " + dut + "\r\n\r\n").encode())
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
if not test_val("Server Error", "505", s.status):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def code_400_bad_request(dut, port):
|
||||||
|
Utility.console_log("[test] 400 Bad Request =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
path = "/hello"
|
||||||
|
s.client.sendall(("XYZ " + path + " HTTP/1.1\r\nHost: " + dut + "\r\n\r\n").encode())
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
if not test_val("Client Error", "400", s.status):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def code_404_not_found(dut, port):
|
||||||
|
Utility.console_log("[test] 404 Not Found =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
path = "/dummy"
|
||||||
|
s.client.sendall(("GET " + path + " HTTP/1.1\r\nHost: " + dut + "\r\n\r\n").encode())
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
if not test_val("Client Error", "404", s.status):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def code_405_method_not_allowed(dut, port):
|
||||||
|
Utility.console_log("[test] 405 Method Not Allowed =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
path = "/hello"
|
||||||
|
s.client.sendall(("POST " + path + " HTTP/1.1\r\nHost: " + dut + "\r\n\r\n").encode())
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
if not test_val("Client Error", "405", s.status):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def code_408_req_timeout(dut, port):
|
||||||
|
Utility.console_log("[test] 408 Request Timeout =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
s.client.sendall(("POST /echo HTTP/1.1\r\nHost: " + dut + "\r\nContent-Length: 10\r\n\r\nABCD").encode())
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
if not test_val("Client Error", "408", s.status):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def code_411_length_required(dut, port):
|
||||||
|
Utility.console_log("[test] 411 Length Required =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
path = "/echo"
|
||||||
|
s.client.sendall(("POST " + path + " HTTP/1.1\r\nHost: " + dut + "\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n").encode())
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
# Presently server sends back 400 Bad Request
|
||||||
|
#if not test_val("Client Error", "411", s.status):
|
||||||
|
#s.close()
|
||||||
|
#return False
|
||||||
|
if not test_val("Client Error", "400", s.status):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def send_getx_uri_len(dut, port, length):
|
||||||
|
s = Session(dut, port)
|
||||||
|
method = "GET "
|
||||||
|
version = " HTTP/1.1\r\n"
|
||||||
|
path = "/"+"x"*(length - len(method) - len(version) - len("/"))
|
||||||
|
s.client.sendall(method.encode())
|
||||||
|
time.sleep(1)
|
||||||
|
s.client.sendall(path.encode())
|
||||||
|
time.sleep(1)
|
||||||
|
s.client.sendall((version + "Host: " + dut + "\r\n\r\n").encode())
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
s.close()
|
||||||
|
return s.status
|
||||||
|
|
||||||
|
def code_414_uri_too_long(dut, port, max_uri_len):
|
||||||
|
Utility.console_log("[test] 414 URI Too Long =>", end=' ')
|
||||||
|
status = send_getx_uri_len(dut, port, max_uri_len)
|
||||||
|
if not test_val("Client Error", "404", status):
|
||||||
|
return False
|
||||||
|
status = send_getx_uri_len(dut, port, max_uri_len + 1)
|
||||||
|
if not test_val("Client Error", "414", status):
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def send_postx_hdr_len(dut, port, length):
|
||||||
|
s = Session(dut, port)
|
||||||
|
path = "/echo"
|
||||||
|
host = "Host: " + dut
|
||||||
|
custom_hdr_field = "\r\nCustom: "
|
||||||
|
custom_hdr_val = "x"*(length - len(host) - len(custom_hdr_field) - len("\r\n\r\n") + len("0"))
|
||||||
|
request = ("POST " + path + " HTTP/1.1\r\n" + host + custom_hdr_field + custom_hdr_val + "\r\n\r\n").encode()
|
||||||
|
s.client.sendall(request[:length//2])
|
||||||
|
time.sleep(1)
|
||||||
|
s.client.sendall(request[length//2:])
|
||||||
|
hdr = s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
s.close()
|
||||||
|
if "Custom" in hdr:
|
||||||
|
return (hdr["Custom"] == custom_hdr_val), resp
|
||||||
|
return False, s.status
|
||||||
|
|
||||||
|
def code_431_hdr_too_long(dut, port, max_hdr_len):
|
||||||
|
Utility.console_log("[test] 431 Header Too Long =>", end=' ')
|
||||||
|
res, status = send_postx_hdr_len(dut, port, max_hdr_len)
|
||||||
|
if not res:
|
||||||
|
return False
|
||||||
|
res, status = send_postx_hdr_len(dut, port, max_hdr_len + 1)
|
||||||
|
if not test_val("Client Error", "431", status):
|
||||||
|
return False
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def test_upgrade_not_supported(dut, port):
|
||||||
|
Utility.console_log("[test] Upgrade Not Supported =>", end=' ')
|
||||||
|
s = Session(dut, port)
|
||||||
|
path = "/hello"
|
||||||
|
s.client.sendall(("OPTIONS * HTTP/1.1\r\nHost:" + dut + "\r\nUpgrade: TLS/1.0\r\nConnection: Upgrade\r\n\r\n").encode())
|
||||||
|
s.read_resp_hdrs()
|
||||||
|
resp = s.read_resp_data()
|
||||||
|
if not test_val("Client Error", "200", s.status):
|
||||||
|
s.close()
|
||||||
|
return False
|
||||||
|
s.close()
|
||||||
|
Utility.console_log("Success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
########### Execution begins here...
|
||||||
|
# Configuration
|
||||||
|
# Max number of threads/sessions
|
||||||
|
max_sessions = 7
|
||||||
|
max_uri_len = 512
|
||||||
|
max_hdr_len = 512
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='Run HTTPD Test')
|
||||||
|
parser.add_argument('-4','--ipv4', help='IPv4 address')
|
||||||
|
parser.add_argument('-6','--ipv6', help='IPv6 address')
|
||||||
|
parser.add_argument('-p','--port', help='Port')
|
||||||
|
args = vars(parser.parse_args())
|
||||||
|
|
||||||
|
dut4 = args['ipv4']
|
||||||
|
dut6 = args['ipv6']
|
||||||
|
port = args['port']
|
||||||
|
dut = dut4
|
||||||
|
|
||||||
|
_verbose_ = True
|
||||||
|
|
||||||
|
Utility.console_log("### Basic HTTP Client Tests")
|
||||||
|
get_hello(dut, port)
|
||||||
|
post_hello(dut, port)
|
||||||
|
put_hello(dut, port)
|
||||||
|
post_echo(dut, port)
|
||||||
|
get_echo(dut, port)
|
||||||
|
put_echo(dut, port)
|
||||||
|
get_hello_type(dut, port)
|
||||||
|
get_hello_status(dut, port)
|
||||||
|
get_false_uri(dut, port)
|
||||||
|
|
||||||
|
Utility.console_log("### Error code tests")
|
||||||
|
code_500_server_error_test(dut, port)
|
||||||
|
code_501_method_not_impl(dut, port)
|
||||||
|
code_505_version_not_supported(dut, port)
|
||||||
|
code_400_bad_request(dut, port)
|
||||||
|
code_404_not_found(dut, port)
|
||||||
|
code_405_method_not_allowed(dut, port)
|
||||||
|
code_408_req_timeout(dut, port)
|
||||||
|
code_414_uri_too_long(dut, port, max_uri_len)
|
||||||
|
code_431_hdr_too_long(dut, port, max_hdr_len)
|
||||||
|
test_upgrade_not_supported(dut, port)
|
||||||
|
|
||||||
|
# Not supported yet (Error on chunked request)
|
||||||
|
###code_411_length_required(dut, port)
|
||||||
|
|
||||||
|
Utility.console_log("### Sessions and Context Tests")
|
||||||
|
parallel_sessions_adder(dut, port, max_sessions)
|
||||||
|
leftover_data_test(dut, port)
|
||||||
|
async_response_test(dut, port)
|
||||||
|
spillover_session(dut, port, max_sessions)
|
||||||
|
recv_timeout_test(dut, port)
|
||||||
|
packet_size_limit_test(dut, port, 50*1024)
|
||||||
|
get_hello(dut, port)
|
||||||
|
|
||||||
|
sys.exit()
|
@ -0,0 +1,7 @@
|
|||||||
|
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||||
|
# in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(persistent_sockets)
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||||
|
# project subdirectory.
|
||||||
|
#
|
||||||
|
|
||||||
|
PROJECT_NAME := persistent_sockets
|
||||||
|
|
||||||
|
include $(IDF_PATH)/make/project.mk
|
||||||
|
|
18
examples/protocols/http_server/persistent_sockets/README.md
Normal file
18
examples/protocols/http_server/persistent_sockets/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# HTTPD Server Persistant Sockets Example
|
||||||
|
|
||||||
|
The Example consists of HTTPD server persistent sockets demo.
|
||||||
|
This sort of persistancy enables the server to have independent sessions/contexts per client.
|
||||||
|
|
||||||
|
* Configure the project using "make menuconfig" and goto :
|
||||||
|
* Example Configuration ->
|
||||||
|
1. WIFI SSID: WIFI network to which your PC is also connected to.
|
||||||
|
2. WIFI Password: WIFI password
|
||||||
|
|
||||||
|
* In order to test the HTTPD server persistent sockets demo :
|
||||||
|
1. compile and burn the firmware "make flash"
|
||||||
|
2. run "make monitor" and note down the IP assigned to your ESP module. The default port is 80
|
||||||
|
3. run the test script "python2 scripts/adder.py \<IP\> \<port\> \<N\>"
|
||||||
|
* the provided test script sends (POST) numbers from 1 to N to the server which has a URI POST handler for adding these numbers into an accumulator that is valid throughout the lifetime of the connection socket, hence persistent
|
||||||
|
* the script does a GET before closing and displays the final value of the accumulator
|
||||||
|
|
||||||
|
See the README.md file in the upper level 'examples' directory for more information about examples.
|
@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from builtins import str
|
||||||
|
from builtins import range
|
||||||
|
import imp
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
|
|
||||||
|
# This environment variable is expected on the host machine
|
||||||
|
test_fw_path = os.getenv("TEST_FW_PATH")
|
||||||
|
if test_fw_path and test_fw_path not in sys.path:
|
||||||
|
sys.path.insert(0, test_fw_path)
|
||||||
|
|
||||||
|
# When running on local machine execute the following before running this script
|
||||||
|
# > make app bootloader
|
||||||
|
# > make print_flash_cmd | tail -n 1 > build/download.config
|
||||||
|
# > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
|
||||||
|
|
||||||
|
import TinyFW
|
||||||
|
import IDF
|
||||||
|
import Utility
|
||||||
|
|
||||||
|
# Import client module
|
||||||
|
expath = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
client = imp.load_source("client", expath + "/scripts/adder.py")
|
||||||
|
|
||||||
|
@IDF.idf_example_test(env_tag="Example_WIFI")
|
||||||
|
def test_examples_protocol_http_server_persistence(env, extra_data):
|
||||||
|
# Acquire DUT
|
||||||
|
dut1 = env.get_dut("http_server", "examples/protocols/http_server/persistent_sockets")
|
||||||
|
|
||||||
|
# Get binary file
|
||||||
|
binary_file = os.path.join(dut1.app.binary_path, "persistent_sockets.bin")
|
||||||
|
bin_size = os.path.getsize(binary_file)
|
||||||
|
IDF.log_performance("http_server_bin_size", "{}KB".format(bin_size//1024))
|
||||||
|
IDF.check_performance("http_server_bin_size", bin_size//1024)
|
||||||
|
|
||||||
|
# Upload binary and start testing
|
||||||
|
Utility.console_log("Starting http_server persistance test app")
|
||||||
|
dut1.start_app()
|
||||||
|
|
||||||
|
# Parse IP address of STA
|
||||||
|
Utility.console_log("Waiting to connect with AP")
|
||||||
|
got_ip = dut1.expect(re.compile(r"(?:[\s\S]*)Got IP: '(\d+.\d+.\d+.\d+)'"), timeout=120)[0]
|
||||||
|
got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Starting server on port: '(\d+)'"), timeout=30)[0]
|
||||||
|
|
||||||
|
Utility.console_log("Got IP : " + got_ip)
|
||||||
|
Utility.console_log("Got Port : " + got_port)
|
||||||
|
|
||||||
|
# Expected Logs
|
||||||
|
dut1.expect("Registering URI handlers", timeout=30)
|
||||||
|
|
||||||
|
# Run test script
|
||||||
|
conn = client.start_session(got_ip, got_port)
|
||||||
|
visitor = 0
|
||||||
|
adder = 0
|
||||||
|
|
||||||
|
# Test PUT request and initialize session context
|
||||||
|
num = random.randint(0,100)
|
||||||
|
client.putreq(conn, "/adder", str(num))
|
||||||
|
visitor += 1
|
||||||
|
dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
|
||||||
|
dut1.expect("/adder PUT handler read " + str(num), timeout=30)
|
||||||
|
dut1.expect("PUT allocating new session", timeout=30)
|
||||||
|
|
||||||
|
# Retest PUT request and change session context value
|
||||||
|
num = random.randint(0,100)
|
||||||
|
Utility.console_log("Adding: " + str(num))
|
||||||
|
client.putreq(conn, "/adder", str(num))
|
||||||
|
visitor += 1
|
||||||
|
adder += num
|
||||||
|
dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
|
||||||
|
dut1.expect("/adder PUT handler read " + str(num), timeout=30)
|
||||||
|
try:
|
||||||
|
# Re allocation shouldn't happen
|
||||||
|
dut1.expect("PUT allocating new session", timeout=30)
|
||||||
|
# Not expected
|
||||||
|
raise RuntimeError
|
||||||
|
except:
|
||||||
|
# As expected
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Test POST request and session persistence
|
||||||
|
random_nums = [random.randint(0,100) for _ in range(100)]
|
||||||
|
for num in random_nums:
|
||||||
|
Utility.console_log("Adding: " + str(num))
|
||||||
|
client.postreq(conn, "/adder", str(num))
|
||||||
|
visitor += 1
|
||||||
|
adder += num
|
||||||
|
dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
|
||||||
|
dut1.expect("/adder handler read " + str(num), timeout=30)
|
||||||
|
|
||||||
|
# Test GET request and session persistence
|
||||||
|
Utility.console_log("Matching final sum: " + str(adder))
|
||||||
|
if client.getreq(conn, "/adder").decode() != str(adder):
|
||||||
|
raise RuntimeError
|
||||||
|
visitor += 1
|
||||||
|
dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
|
||||||
|
dut1.expect("/adder GET handler send " + str(adder), timeout=30)
|
||||||
|
|
||||||
|
Utility.console_log("Ending session")
|
||||||
|
# Close connection and check for invocation of context "Free" function
|
||||||
|
client.end_session(conn)
|
||||||
|
dut1.expect("/adder Free Context function called", timeout=30)
|
||||||
|
|
||||||
|
Utility.console_log("Validating user context data")
|
||||||
|
# Start another session to check user context data
|
||||||
|
conn2 = client.start_session(got_ip, got_port)
|
||||||
|
num = random.randint(0,100)
|
||||||
|
client.putreq(conn, "/adder", str(num))
|
||||||
|
visitor += 1
|
||||||
|
dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
|
||||||
|
dut1.expect("/adder PUT handler read " + str(num), timeout=30)
|
||||||
|
dut1.expect("PUT allocating new session", timeout=30)
|
||||||
|
client.end_session(conn)
|
||||||
|
dut1.expect("/adder Free Context function called", timeout=30)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_examples_protocol_http_server_persistence()
|
@ -0,0 +1,4 @@
|
|||||||
|
set(COMPONENT_SRCS "main.c")
|
||||||
|
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||||
|
|
||||||
|
register_component()
|
@ -0,0 +1,16 @@
|
|||||||
|
menu "Example Configuration"
|
||||||
|
|
||||||
|
config WIFI_SSID
|
||||||
|
string "WiFi SSID"
|
||||||
|
default "myssid"
|
||||||
|
help
|
||||||
|
SSID (network name) for the example to connect to.
|
||||||
|
|
||||||
|
config WIFI_PASSWORD
|
||||||
|
string "WiFi Password"
|
||||||
|
default "mypassword"
|
||||||
|
help
|
||||||
|
WiFi password (WPA or WPA2) for the example to use.
|
||||||
|
Can be left blank if the network has no security set.
|
||||||
|
|
||||||
|
endmenu
|
@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
# "main" pseudo-component makefile.
|
||||||
|
#
|
||||||
|
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||||
|
|
252
examples/protocols/http_server/persistent_sockets/main/main.c
Normal file
252
examples/protocols/http_server/persistent_sockets/main/main.c
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
/* Persistent Sockets Example
|
||||||
|
|
||||||
|
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, this
|
||||||
|
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <esp_wifi.h>
|
||||||
|
#include <esp_event_loop.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
#include <nvs_flash.h>
|
||||||
|
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
|
||||||
|
/* An example to demonstrate persistent sockets, with context maintained across
|
||||||
|
* multiple requests on that socket.
|
||||||
|
* The examples use simple WiFi configuration that you can set via 'make menuconfig'.
|
||||||
|
* If you'd rather not, just change the below entries to strings with
|
||||||
|
* the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
|
||||||
|
*/
|
||||||
|
#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
|
||||||
|
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
|
||||||
|
|
||||||
|
static const char *TAG="APP";
|
||||||
|
|
||||||
|
/* Function to free context */
|
||||||
|
void adder_free_func(void *ctx)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "/adder Free Context function called");
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This handler keeps accumulating data that is posted to it into a per
|
||||||
|
* socket/session context. And returns the result.
|
||||||
|
*/
|
||||||
|
esp_err_t adder_post_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
/* Log total visitors */
|
||||||
|
unsigned *visitors = (unsigned *)req->user_ctx;
|
||||||
|
ESP_LOGI(TAG, "/adder visitor count = %d", ++(*visitors));
|
||||||
|
|
||||||
|
char buf[10];
|
||||||
|
char outbuf[50];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Read data received in the request */
|
||||||
|
ret = httpd_req_recv(req, buf, sizeof(buf));
|
||||||
|
if (ret <= 0) {
|
||||||
|
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||||
|
httpd_resp_send_408(req);
|
||||||
|
}
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[ret] = '\0';
|
||||||
|
int val = atoi(buf);
|
||||||
|
ESP_LOGI(TAG, "/adder handler read %d", val);
|
||||||
|
|
||||||
|
/* Create session's context if not already available */
|
||||||
|
if (! req->sess_ctx) {
|
||||||
|
ESP_LOGI(TAG, "/adder allocating new session");
|
||||||
|
req->sess_ctx = malloc(sizeof(int));
|
||||||
|
req->free_ctx = adder_free_func;
|
||||||
|
*(int *)req->sess_ctx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add the received data to the context */
|
||||||
|
int *adder = (int *)req->sess_ctx;
|
||||||
|
*adder += val;
|
||||||
|
|
||||||
|
/* Respond with the accumulated value */
|
||||||
|
snprintf(outbuf, sizeof(outbuf),"%d", *adder);
|
||||||
|
httpd_resp_send(req, outbuf, strlen(outbuf));
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This handler gets the present value of the accumulator */
|
||||||
|
esp_err_t adder_get_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
/* Log total visitors */
|
||||||
|
unsigned *visitors = (unsigned *)req->user_ctx;
|
||||||
|
ESP_LOGI(TAG, "/adder visitor count = %d", ++(*visitors));
|
||||||
|
|
||||||
|
char outbuf[50];
|
||||||
|
|
||||||
|
/* Create session's context if not already available */
|
||||||
|
if (! req->sess_ctx) {
|
||||||
|
ESP_LOGI(TAG, "/adder GET allocating new session");
|
||||||
|
req->sess_ctx = malloc(sizeof(int));
|
||||||
|
req->free_ctx = adder_free_func;
|
||||||
|
*(int *)req->sess_ctx = 0;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "/adder GET handler send %d", *(int *)req->sess_ctx);
|
||||||
|
|
||||||
|
/* Respond with the accumulated value */
|
||||||
|
snprintf(outbuf, sizeof(outbuf),"%d", *((int *)req->sess_ctx));
|
||||||
|
httpd_resp_send(req, outbuf, strlen(outbuf));
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This handler resets the value of the accumulator */
|
||||||
|
esp_err_t adder_put_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
/* Log total visitors */
|
||||||
|
unsigned *visitors = (unsigned *)req->user_ctx;
|
||||||
|
ESP_LOGI(TAG, "/adder visitor count = %d", ++(*visitors));
|
||||||
|
|
||||||
|
char buf[10];
|
||||||
|
char outbuf[50];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Read data received in the request */
|
||||||
|
ret = httpd_req_recv(req, buf, sizeof(buf));
|
||||||
|
if (ret <= 0) {
|
||||||
|
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||||
|
httpd_resp_send_408(req);
|
||||||
|
}
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[ret] = '\0';
|
||||||
|
int val = atoi(buf);
|
||||||
|
ESP_LOGI(TAG, "/adder PUT handler read %d", val);
|
||||||
|
|
||||||
|
/* Create session's context if not already available */
|
||||||
|
if (! req->sess_ctx) {
|
||||||
|
ESP_LOGI(TAG, "/adder PUT allocating new session");
|
||||||
|
req->sess_ctx = malloc(sizeof(int));
|
||||||
|
req->free_ctx = adder_free_func;
|
||||||
|
}
|
||||||
|
*(int *)req->sess_ctx = val;
|
||||||
|
|
||||||
|
/* Respond with the reset value */
|
||||||
|
snprintf(outbuf, sizeof(outbuf),"%d", *((int *)req->sess_ctx));
|
||||||
|
httpd_resp_send(req, outbuf, strlen(outbuf));
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Maintain a variable which stores the number of times
|
||||||
|
* the "/adder" URI has been visited */
|
||||||
|
static unsigned visitors = 0;
|
||||||
|
|
||||||
|
httpd_uri_t adder_post = {
|
||||||
|
.uri = "/adder",
|
||||||
|
.method = HTTP_POST,
|
||||||
|
.handler = adder_post_handler,
|
||||||
|
.user_ctx = &visitors
|
||||||
|
};
|
||||||
|
|
||||||
|
httpd_uri_t adder_get = {
|
||||||
|
.uri = "/adder",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = adder_get_handler,
|
||||||
|
.user_ctx = &visitors
|
||||||
|
};
|
||||||
|
|
||||||
|
httpd_uri_t adder_put = {
|
||||||
|
.uri = "/adder",
|
||||||
|
.method = HTTP_PUT,
|
||||||
|
.handler = adder_put_handler,
|
||||||
|
.user_ctx = &visitors
|
||||||
|
};
|
||||||
|
|
||||||
|
httpd_handle_t start_webserver(void)
|
||||||
|
{
|
||||||
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
|
// Start the httpd server
|
||||||
|
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
|
||||||
|
httpd_handle_t server;
|
||||||
|
|
||||||
|
if (httpd_start(&server, &config) == ESP_OK) {
|
||||||
|
// Set URI handlers
|
||||||
|
ESP_LOGI(TAG, "Registering URI handlers");
|
||||||
|
httpd_register_uri_handler(server, &adder_get);
|
||||||
|
httpd_register_uri_handler(server, &adder_put);
|
||||||
|
httpd_register_uri_handler(server, &adder_post);
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Error starting server!");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop_webserver(httpd_handle_t server)
|
||||||
|
{
|
||||||
|
// Stop the httpd server
|
||||||
|
httpd_stop(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t event_handler(void *ctx, system_event_t *event)
|
||||||
|
{
|
||||||
|
httpd_handle_t *server = (httpd_handle_t *) ctx;
|
||||||
|
|
||||||
|
switch(event->event_id) {
|
||||||
|
case SYSTEM_EVENT_STA_START:
|
||||||
|
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_connect());
|
||||||
|
break;
|
||||||
|
case SYSTEM_EVENT_STA_GOT_IP:
|
||||||
|
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
|
||||||
|
ESP_LOGI(TAG, "Got IP: '%s'",
|
||||||
|
ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
|
||||||
|
|
||||||
|
/* Start the web server */
|
||||||
|
if (*server == NULL) {
|
||||||
|
*server = start_webserver();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||||
|
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_connect());
|
||||||
|
|
||||||
|
/* Stop the webserver */
|
||||||
|
if (*server) {
|
||||||
|
stop_webserver(*server);
|
||||||
|
*server = NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialise_wifi(void *arg)
|
||||||
|
{
|
||||||
|
tcpip_adapter_init();
|
||||||
|
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, arg));
|
||||||
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
||||||
|
wifi_config_t wifi_config = {
|
||||||
|
.sta = {
|
||||||
|
.ssid = EXAMPLE_WIFI_SSID,
|
||||||
|
.password = EXAMPLE_WIFI_PASS,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_start());
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main()
|
||||||
|
{
|
||||||
|
static httpd_handle_t server = NULL;
|
||||||
|
ESP_ERROR_CHECK(nvs_flash_init());
|
||||||
|
initialise_wifi(&server);
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from future import standard_library
|
||||||
|
standard_library.install_aliases()
|
||||||
|
from builtins import str
|
||||||
|
from builtins import range
|
||||||
|
import http.client
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
def start_session (ip, port):
|
||||||
|
return http.client.HTTPConnection(ip, int(port), timeout=15)
|
||||||
|
|
||||||
|
def end_session (conn):
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def getreq (conn, path, verbose = False):
|
||||||
|
conn.request("GET", path)
|
||||||
|
resp = conn.getresponse()
|
||||||
|
data = resp.read()
|
||||||
|
if verbose:
|
||||||
|
Utility.console_log("GET : " + path)
|
||||||
|
Utility.console_log("Status : " + resp.status)
|
||||||
|
Utility.console_log("Reason : " + resp.reason)
|
||||||
|
Utility.console_log("Data length : " + str(len(data)))
|
||||||
|
Utility.console_log("Data content : " + data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def postreq (conn, path, data, verbose = False):
|
||||||
|
conn.request("POST", path, data)
|
||||||
|
resp = conn.getresponse()
|
||||||
|
data = resp.read()
|
||||||
|
if verbose:
|
||||||
|
Utility.console_log("POST : " + data)
|
||||||
|
Utility.console_log("Status : " + resp.status)
|
||||||
|
Utility.console_log("Reason : " + resp.reason)
|
||||||
|
Utility.console_log("Data length : " + str(len(data)))
|
||||||
|
Utility.console_log("Data content : " + data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def putreq (conn, path, body, verbose = False):
|
||||||
|
conn.request("PUT", path, body)
|
||||||
|
resp = conn.getresponse()
|
||||||
|
data = resp.read()
|
||||||
|
if verbose:
|
||||||
|
Utility.console_log("PUT : " + path, body)
|
||||||
|
Utility.console_log("Status : " + resp.status)
|
||||||
|
Utility.console_log("Reason : " + resp.reason)
|
||||||
|
Utility.console_log("Data length : " + str(len(data)))
|
||||||
|
Utility.console_log("Data content : " + data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Configure argument parser
|
||||||
|
parser = argparse.ArgumentParser(description='Run HTTPd Test')
|
||||||
|
parser.add_argument('IP' , metavar='IP' , type=str, help='Server IP')
|
||||||
|
parser.add_argument('port', metavar='port', type=str, help='Server port')
|
||||||
|
parser.add_argument('N' , metavar='integer', type=int, help='Integer to sum upto')
|
||||||
|
args = vars(parser.parse_args())
|
||||||
|
|
||||||
|
# Get arguments
|
||||||
|
ip = args['IP']
|
||||||
|
port = args['port']
|
||||||
|
N = args['N']
|
||||||
|
|
||||||
|
# Establish HTTP connection
|
||||||
|
Utility.console_log("Connecting to => " + ip + ":" + port)
|
||||||
|
conn = start_session (ip, port)
|
||||||
|
|
||||||
|
# Reset adder context to specified value(0)
|
||||||
|
# -- Not needed as new connection will always
|
||||||
|
# -- have zero value of the accumulator
|
||||||
|
Utility.console_log("Reset the accumulator to 0")
|
||||||
|
putreq (conn, "/adder", str(0))
|
||||||
|
|
||||||
|
# Sum numbers from 1 to specified value(N)
|
||||||
|
Utility.console_log("Summing numbers from 1 to " + str(N))
|
||||||
|
for i in range(1, N+1):
|
||||||
|
postreq (conn, "/adder", str(i))
|
||||||
|
|
||||||
|
# Fetch the result
|
||||||
|
Utility.console_log("Result :" + getreq (conn, "/adder"))
|
||||||
|
|
||||||
|
# Close HTTP connection
|
||||||
|
end_session (conn)
|
6
examples/protocols/http_server/simple/CMakeLists.txt
Normal file
6
examples/protocols/http_server/simple/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||||
|
# in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(simple)
|
9
examples/protocols/http_server/simple/Makefile
Normal file
9
examples/protocols/http_server/simple/Makefile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||||
|
# project subdirectory.
|
||||||
|
#
|
||||||
|
|
||||||
|
PROJECT_NAME := simple
|
||||||
|
|
||||||
|
include $(IDF_PATH)/make/project.mk
|
||||||
|
|
27
examples/protocols/http_server/simple/README.md
Normal file
27
examples/protocols/http_server/simple/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Simple HTTPD Server Example
|
||||||
|
|
||||||
|
The Example consists of HTTPD server demo with demostration of URI handling :
|
||||||
|
1. URI \hello for GET command returns "Hello World!" message
|
||||||
|
2. URI \echo for POST command echoes back the POSTed message
|
||||||
|
|
||||||
|
* Configure the project using "make menuconfig" and goto :
|
||||||
|
* Example Configuration ->
|
||||||
|
1. WIFI SSID: WIFI network to which your PC is also connected to.
|
||||||
|
2. WIFI Password: WIFI password
|
||||||
|
|
||||||
|
* In order to test the HTTPD server persistent sockets demo :
|
||||||
|
1. compile and burn the firmware "make flash"
|
||||||
|
2. run "make monitor" and note down the IP assigned to your ESP module. The default port is 80
|
||||||
|
3. test the example :
|
||||||
|
* run the test script : "python2 scripts/client.py \<IP\> \<port\> \<MSG\>"
|
||||||
|
* the provided test script first does a GET \hello and displays the response
|
||||||
|
* the script does a POST to \echo with the user input \<MSG\> and displays the response
|
||||||
|
* or use curl (asssuming IP is 192.168.43.130):
|
||||||
|
1. "curl 192.168.43.130:80/hello" - tests the GET "\hello" handler
|
||||||
|
2. "curl -X POST --data-binary @anyfile 192.168.43.130:80/echo > tmpfile"
|
||||||
|
* "anyfile" is the file being sent as request body and "tmpfile" is where the body of the response is saved
|
||||||
|
* since the server echoes back the request body, the two files should be same, as can be confirmed using : "cmp anyfile tmpfile"
|
||||||
|
3. "curl -X PUT -d "0" 192.168.43.130:80/ctrl" - disable /hello and /echo handlers
|
||||||
|
4. "curl -X PUT -d "1" 192.168.43.130:80/ctrl" - enable /hello and /echo handlers
|
||||||
|
|
||||||
|
See the README.md file in the upper level 'examples' directory for more information about examples.
|
121
examples/protocols/http_server/simple/http_server_simple_test.py
Normal file
121
examples/protocols/http_server/simple/http_server_simple_test.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from builtins import range
|
||||||
|
import imp
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
|
|
||||||
|
# This environment variable is expected on the host machine
|
||||||
|
test_fw_path = os.getenv("TEST_FW_PATH")
|
||||||
|
if test_fw_path and test_fw_path not in sys.path:
|
||||||
|
sys.path.insert(0, test_fw_path)
|
||||||
|
|
||||||
|
# When running on local machine execute the following before running this script
|
||||||
|
# > make app bootloader
|
||||||
|
# > make print_flash_cmd | tail -n 1 > build/download.config
|
||||||
|
# > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
|
||||||
|
|
||||||
|
import TinyFW
|
||||||
|
import IDF
|
||||||
|
import Utility
|
||||||
|
|
||||||
|
# Import client module
|
||||||
|
expath = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
client = imp.load_source("client", expath + "/scripts/client.py")
|
||||||
|
|
||||||
|
@IDF.idf_example_test(env_tag="Example_WIFI")
|
||||||
|
def test_examples_protocol_http_server_simple(env, extra_data):
|
||||||
|
# Acquire DUT
|
||||||
|
dut1 = env.get_dut("http_server", "examples/protocols/http_server/simple")
|
||||||
|
|
||||||
|
# Get binary file
|
||||||
|
binary_file = os.path.join(dut1.app.binary_path, "simple.bin")
|
||||||
|
bin_size = os.path.getsize(binary_file)
|
||||||
|
IDF.log_performance("http_server_bin_size", "{}KB".format(bin_size//1024))
|
||||||
|
IDF.check_performance("http_server_bin_size", bin_size//1024)
|
||||||
|
|
||||||
|
# Upload binary and start testing
|
||||||
|
Utility.console_log("Starting http_server simple test app")
|
||||||
|
dut1.start_app()
|
||||||
|
|
||||||
|
# Parse IP address of STA
|
||||||
|
Utility.console_log("Waiting to connect with AP")
|
||||||
|
got_ip = dut1.expect(re.compile(r"(?:[\s\S]*)Got IP: '(\d+.\d+.\d+.\d+)'"), timeout=120)[0]
|
||||||
|
got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Starting server on port: '(\d+)'"), timeout=30)[0]
|
||||||
|
|
||||||
|
Utility.console_log("Got IP : " + got_ip)
|
||||||
|
Utility.console_log("Got Port : " + got_port)
|
||||||
|
|
||||||
|
# Expected Logs
|
||||||
|
dut1.expect("Registering URI handlers", timeout=30)
|
||||||
|
|
||||||
|
# Run test script
|
||||||
|
# If failed raise appropriate exception
|
||||||
|
Utility.console_log("Test /hello GET handler")
|
||||||
|
if not client.test_get_handler(got_ip, got_port):
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
# Acquire host IP. Need a way to check it
|
||||||
|
host_ip = dut1.expect(re.compile(r"(?:[\s\S]*)Found header => Host: (\d+.\d+.\d+.\d+)"), timeout=30)[0]
|
||||||
|
|
||||||
|
# Match additional headers sent in the request
|
||||||
|
dut1.expect("Found header => Test-Header-2: Test-Value-2", timeout=30)
|
||||||
|
dut1.expect("Found header => Test-Header-1: Test-Value-1", timeout=30)
|
||||||
|
dut1.expect("Found URL query parameter => query1=value1", timeout=30)
|
||||||
|
dut1.expect("Found URL query parameter => query3=value3", timeout=30)
|
||||||
|
dut1.expect("Found URL query parameter => query2=value2", timeout=30)
|
||||||
|
dut1.expect("Request headers lost", timeout=30)
|
||||||
|
|
||||||
|
Utility.console_log("Test /ctrl PUT handler and realtime handler de/registration")
|
||||||
|
if not client.test_put_handler(got_ip, got_port):
|
||||||
|
raise RuntimeError
|
||||||
|
dut1.expect("Unregistering /hello and /echo URIs", timeout=30)
|
||||||
|
dut1.expect("Registering /hello and /echo URIs", timeout=30)
|
||||||
|
|
||||||
|
# Generate random data of 10KB
|
||||||
|
random_data = ''.join(string.printable[random.randint(0,len(string.printable))-1] for _ in range(10*1024))
|
||||||
|
Utility.console_log("Test /echo POST handler with random data")
|
||||||
|
if not client.test_post_handler(got_ip, got_port, random_data):
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
query = "http://foobar"
|
||||||
|
Utility.console_log("Test /hello with custom query : " + query)
|
||||||
|
if not client.test_custom_uri_query(got_ip, got_port, query):
|
||||||
|
raise RuntimeError
|
||||||
|
dut1.expect("Found URL query => " + query, timeout=30)
|
||||||
|
|
||||||
|
query = "abcd+1234%20xyz"
|
||||||
|
Utility.console_log("Test /hello with custom query : " + query)
|
||||||
|
if not client.test_custom_uri_query(got_ip, got_port, query):
|
||||||
|
raise RuntimeError
|
||||||
|
dut1.expect("Found URL query => " + query, timeout=30)
|
||||||
|
|
||||||
|
query = "abcd\nyz"
|
||||||
|
Utility.console_log("Test /hello with invalid query")
|
||||||
|
if client.test_custom_uri_query(got_ip, got_port, query):
|
||||||
|
raise RuntimeError
|
||||||
|
dut1.expect("400 Bad Request - Server unable to understand request due to invalid syntax", timeout=30)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_examples_protocol_http_server_simple()
|
@ -0,0 +1,4 @@
|
|||||||
|
set(COMPONENT_SRCS "main.c")
|
||||||
|
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||||
|
|
||||||
|
register_component()
|
16
examples/protocols/http_server/simple/main/Kconfig.projbuild
Normal file
16
examples/protocols/http_server/simple/main/Kconfig.projbuild
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
menu "Example Configuration"
|
||||||
|
|
||||||
|
config WIFI_SSID
|
||||||
|
string "WiFi SSID"
|
||||||
|
default "myssid"
|
||||||
|
help
|
||||||
|
SSID (network name) for the example to connect to.
|
||||||
|
|
||||||
|
config WIFI_PASSWORD
|
||||||
|
string "WiFi Password"
|
||||||
|
default "mypassword"
|
||||||
|
help
|
||||||
|
WiFi password (WPA or WPA2) for the example to use.
|
||||||
|
Can be left blank if the network has no security set.
|
||||||
|
|
||||||
|
endmenu
|
5
examples/protocols/http_server/simple/main/component.mk
Normal file
5
examples/protocols/http_server/simple/main/component.mk
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
# "main" pseudo-component makefile.
|
||||||
|
#
|
||||||
|
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||||
|
|
279
examples/protocols/http_server/simple/main/main.c
Normal file
279
examples/protocols/http_server/simple/main/main.c
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
/* Simple HTTP Server Example
|
||||||
|
|
||||||
|
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, this
|
||||||
|
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <esp_wifi.h>
|
||||||
|
#include <esp_event_loop.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
#include <nvs_flash.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
|
||||||
|
/* A simple example that demonstrates how to create GET and POST
|
||||||
|
* handlers for the web server.
|
||||||
|
* The examples use simple WiFi configuration that you can set via
|
||||||
|
* 'make menuconfig'.
|
||||||
|
* If you'd rather not, just change the below entries to strings
|
||||||
|
* with the config you want -
|
||||||
|
* ie. #define EXAMPLE_WIFI_SSID "mywifissid"
|
||||||
|
*/
|
||||||
|
#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
|
||||||
|
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
|
||||||
|
|
||||||
|
static const char *TAG="APP";
|
||||||
|
|
||||||
|
/* An HTTP GET handler */
|
||||||
|
esp_err_t hello_get_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
char* buf;
|
||||||
|
size_t buf_len;
|
||||||
|
|
||||||
|
/* Get header value string length and allocate memory for length + 1,
|
||||||
|
* extra byte for null termination */
|
||||||
|
buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
|
||||||
|
if (buf_len > 1) {
|
||||||
|
buf = malloc(buf_len);
|
||||||
|
/* Copy null terminated value string into buffer */
|
||||||
|
if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Found header => Host: %s", buf);
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-2") + 1;
|
||||||
|
if (buf_len > 1) {
|
||||||
|
buf = malloc(buf_len);
|
||||||
|
if (httpd_req_get_hdr_value_str(req, "Test-Header-2", buf, buf_len) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Found header => Test-Header-2: %s", buf);
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-1") + 1;
|
||||||
|
if (buf_len > 1) {
|
||||||
|
buf = malloc(buf_len);
|
||||||
|
if (httpd_req_get_hdr_value_str(req, "Test-Header-1", buf, buf_len) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Found header => Test-Header-1: %s", buf);
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read URL query string length and allocate memory for length + 1,
|
||||||
|
* extra byte for null termination */
|
||||||
|
buf_len = httpd_req_get_url_query_len(req) + 1;
|
||||||
|
if (buf_len > 1) {
|
||||||
|
buf = malloc(buf_len);
|
||||||
|
if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Found URL query => %s", buf);
|
||||||
|
char param[32];
|
||||||
|
/* Get value of expected key from query string */
|
||||||
|
if (httpd_query_key_value(buf, "query1", param, sizeof(param)) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Found URL query parameter => query1=%s", param);
|
||||||
|
}
|
||||||
|
if (httpd_query_key_value(buf, "query3", param, sizeof(param)) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Found URL query parameter => query3=%s", param);
|
||||||
|
}
|
||||||
|
if (httpd_query_key_value(buf, "query2", param, sizeof(param)) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Found URL query parameter => query2=%s", param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set some custom headers */
|
||||||
|
httpd_resp_set_hdr(req, "Custom-Header-1", "Custom-Value-1");
|
||||||
|
httpd_resp_set_hdr(req, "Custom-Header-2", "Custom-Value-2");
|
||||||
|
|
||||||
|
/* Send response with custom headers and body set as the
|
||||||
|
* string passed in user context*/
|
||||||
|
const char* resp_str = (const char*) req->user_ctx;
|
||||||
|
httpd_resp_send(req, resp_str, strlen(resp_str));
|
||||||
|
|
||||||
|
/* After sending the HTTP response the old HTTP request
|
||||||
|
* headers are lost. Check if HTTP request headers can be read now. */
|
||||||
|
if (httpd_req_get_hdr_value_len(req, "Host") == 0) {
|
||||||
|
ESP_LOGI(TAG, "Request headers lost");
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_uri_t hello = {
|
||||||
|
.uri = "/hello",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = hello_get_handler,
|
||||||
|
/* Let's pass response string in user
|
||||||
|
* context to demonstrate it's usage */
|
||||||
|
.user_ctx = "Hello World!"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* An HTTP POST handler */
|
||||||
|
esp_err_t echo_post_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
char buf[100];
|
||||||
|
int ret, remaining = req->content_len;
|
||||||
|
|
||||||
|
while (remaining > 0) {
|
||||||
|
/* Read the data for the request */
|
||||||
|
if ((ret = httpd_req_recv(req, buf,
|
||||||
|
MIN(remaining, sizeof(buf)))) <= 0) {
|
||||||
|
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||||
|
/* Retry receiving if timeout occurred */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send back the same data */
|
||||||
|
httpd_resp_send_chunk(req, buf, ret);
|
||||||
|
remaining -= ret;
|
||||||
|
|
||||||
|
/* Log data received */
|
||||||
|
ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
|
||||||
|
ESP_LOGI(TAG, "%.*s", ret, buf);
|
||||||
|
ESP_LOGI(TAG, "====================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
// End response
|
||||||
|
httpd_resp_send_chunk(req, NULL, 0);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_uri_t echo = {
|
||||||
|
.uri = "/echo",
|
||||||
|
.method = HTTP_POST,
|
||||||
|
.handler = echo_post_handler,
|
||||||
|
.user_ctx = NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
/* An HTTP PUT handler. This demonstrates realtime
|
||||||
|
* registration and deregistration of URI handlers
|
||||||
|
*/
|
||||||
|
esp_err_t ctrl_put_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
char buf;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if ((ret = httpd_req_recv(req, &buf, 1)) <= 0) {
|
||||||
|
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||||
|
httpd_resp_send_408(req);
|
||||||
|
}
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf == '0') {
|
||||||
|
/* Handler can be unregistered using the uri string */
|
||||||
|
ESP_LOGI(TAG, "Unregistering /hello and /echo URIs");
|
||||||
|
httpd_unregister_uri(req->handle, "/hello");
|
||||||
|
httpd_unregister_uri(req->handle, "/echo");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ESP_LOGI(TAG, "Registering /hello and /echo URIs");
|
||||||
|
httpd_register_uri_handler(req->handle, &hello);
|
||||||
|
httpd_register_uri_handler(req->handle, &echo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Respond with empty body */
|
||||||
|
httpd_resp_send(req, NULL, 0);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_uri_t ctrl = {
|
||||||
|
.uri = "/ctrl",
|
||||||
|
.method = HTTP_PUT,
|
||||||
|
.handler = ctrl_put_handler,
|
||||||
|
.user_ctx = NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
httpd_handle_t start_webserver(void)
|
||||||
|
{
|
||||||
|
httpd_handle_t server = NULL;
|
||||||
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
|
|
||||||
|
// Start the httpd server
|
||||||
|
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
|
||||||
|
if (httpd_start(&server, &config) == ESP_OK) {
|
||||||
|
// Set URI handlers
|
||||||
|
ESP_LOGI(TAG, "Registering URI handlers");
|
||||||
|
httpd_register_uri_handler(server, &hello);
|
||||||
|
httpd_register_uri_handler(server, &echo);
|
||||||
|
httpd_register_uri_handler(server, &ctrl);
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Error starting server!");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop_webserver(httpd_handle_t server)
|
||||||
|
{
|
||||||
|
// Stop the httpd server
|
||||||
|
httpd_stop(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t event_handler(void *ctx, system_event_t *event)
|
||||||
|
{
|
||||||
|
httpd_handle_t *server = (httpd_handle_t *) ctx;
|
||||||
|
|
||||||
|
switch(event->event_id) {
|
||||||
|
case SYSTEM_EVENT_STA_START:
|
||||||
|
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_connect());
|
||||||
|
break;
|
||||||
|
case SYSTEM_EVENT_STA_GOT_IP:
|
||||||
|
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
|
||||||
|
ESP_LOGI(TAG, "Got IP: '%s'",
|
||||||
|
ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
|
||||||
|
|
||||||
|
/* Start the web server */
|
||||||
|
if (*server == NULL) {
|
||||||
|
*server = start_webserver();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||||
|
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_connect());
|
||||||
|
|
||||||
|
/* Stop the web server */
|
||||||
|
if (*server) {
|
||||||
|
stop_webserver(*server);
|
||||||
|
*server = NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialise_wifi(void *arg)
|
||||||
|
{
|
||||||
|
tcpip_adapter_init();
|
||||||
|
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, arg));
|
||||||
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
||||||
|
wifi_config_t wifi_config = {
|
||||||
|
.sta = {
|
||||||
|
.ssid = EXAMPLE_WIFI_SSID,
|
||||||
|
.password = EXAMPLE_WIFI_PASS,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_start());
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main()
|
||||||
|
{
|
||||||
|
static httpd_handle_t server = NULL;
|
||||||
|
ESP_ERROR_CHECK(nvs_flash_init());
|
||||||
|
initialise_wifi(&server);
|
||||||
|
}
|
157
examples/protocols/http_server/simple/scripts/client.py
Normal file
157
examples/protocols/http_server/simple/scripts/client.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from future import standard_library
|
||||||
|
standard_library.install_aliases()
|
||||||
|
from builtins import str
|
||||||
|
import http.client
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
def verbose_print(verbosity, *args):
|
||||||
|
if (verbosity):
|
||||||
|
Utility.console_log(''.join(str(elems) for elems in args))
|
||||||
|
|
||||||
|
def test_get_handler(ip, port, verbosity = False):
|
||||||
|
verbose_print(verbosity, "======== GET HANDLER TEST =============")
|
||||||
|
# Establish HTTP connection
|
||||||
|
verbose_print(verbosity, "Connecting to => " + ip + ":" + port)
|
||||||
|
sess = http.client.HTTPConnection(ip + ":" + port, timeout = 15)
|
||||||
|
|
||||||
|
uri = "/hello?query1=value1&query2=value2&query3=value3"
|
||||||
|
# GET hello response
|
||||||
|
test_headers = {"Test-Header-1":"Test-Value-1", "Test-Header-2":"Test-Value-2"}
|
||||||
|
verbose_print(verbosity, "Sending GET to URI : ", uri)
|
||||||
|
verbose_print(verbosity, "Sending additional headers : ")
|
||||||
|
for k, v in test_headers.items():
|
||||||
|
verbose_print(verbosity, "\t", k, ": ", v)
|
||||||
|
sess.request("GET", url=uri, headers=test_headers)
|
||||||
|
resp = sess.getresponse()
|
||||||
|
resp_hdrs = resp.getheaders()
|
||||||
|
resp_data = resp.read().decode()
|
||||||
|
try:
|
||||||
|
if resp.getheader("Custom-Header-1") != "Custom-Value-1":
|
||||||
|
return False
|
||||||
|
if resp.getheader("Custom-Header-2") != "Custom-Value-2":
|
||||||
|
return False
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
verbose_print(verbosity, "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv")
|
||||||
|
verbose_print(verbosity, "Server response to GET /hello")
|
||||||
|
verbose_print(verbosity, "Response Headers : ")
|
||||||
|
for k, v in resp_hdrs:
|
||||||
|
verbose_print(verbosity, "\t", k, ": ", v)
|
||||||
|
verbose_print(verbosity, "Response Data : " + resp_data)
|
||||||
|
verbose_print(verbosity, "========================================\n")
|
||||||
|
|
||||||
|
# Close HTTP connection
|
||||||
|
sess.close()
|
||||||
|
return (resp_data == "Hello World!")
|
||||||
|
|
||||||
|
def test_post_handler(ip, port, msg, verbosity = False):
|
||||||
|
verbose_print(verbosity, "======== POST HANDLER TEST ============")
|
||||||
|
# Establish HTTP connection
|
||||||
|
verbose_print(verbosity, "Connecting to => " + ip + ":" + port)
|
||||||
|
sess = http.client.HTTPConnection(ip + ":" + port, timeout = 15)
|
||||||
|
|
||||||
|
# POST message to /echo and get back response
|
||||||
|
sess.request("POST", url="/echo", body=msg)
|
||||||
|
resp = sess.getresponse()
|
||||||
|
resp_data = resp.read().decode()
|
||||||
|
verbose_print(verbosity, "Server response to POST /echo (" + msg + ")")
|
||||||
|
verbose_print(verbosity, "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv")
|
||||||
|
verbose_print(verbosity, resp_data)
|
||||||
|
verbose_print(verbosity, "========================================\n")
|
||||||
|
|
||||||
|
# Close HTTP connection
|
||||||
|
sess.close()
|
||||||
|
return (resp_data == msg)
|
||||||
|
|
||||||
|
def test_put_handler(ip, port, verbosity = False):
|
||||||
|
verbose_print(verbosity, "======== PUT HANDLER TEST =============")
|
||||||
|
# Establish HTTP connection
|
||||||
|
verbose_print(verbosity, "Connecting to => " + ip + ":" + port)
|
||||||
|
sess = http.client.HTTPConnection(ip + ":" + port, timeout = 15)
|
||||||
|
|
||||||
|
# PUT message to /ctrl to disable /hello URI handler
|
||||||
|
verbose_print(verbosity, "Disabling /hello handler")
|
||||||
|
sess.request("PUT", url="/ctrl", body="0")
|
||||||
|
resp = sess.getresponse()
|
||||||
|
resp.read()
|
||||||
|
|
||||||
|
sess.request("GET", url="/hello")
|
||||||
|
resp = sess.getresponse()
|
||||||
|
resp_data1 = resp.read().decode()
|
||||||
|
verbose_print(verbosity, "Response on GET /hello : " + resp_data1)
|
||||||
|
|
||||||
|
# PUT message to /ctrl to enable /hello URI handler
|
||||||
|
verbose_print(verbosity, "Enabling /hello handler")
|
||||||
|
sess.request("PUT", url="/ctrl", body="1")
|
||||||
|
resp = sess.getresponse()
|
||||||
|
resp.read()
|
||||||
|
|
||||||
|
sess.request("GET", url="/hello")
|
||||||
|
resp = sess.getresponse()
|
||||||
|
resp_data2 = resp.read().decode()
|
||||||
|
verbose_print(verbosity, "Response on GET /hello : " + resp_data2)
|
||||||
|
|
||||||
|
# Close HTTP connection
|
||||||
|
sess.close()
|
||||||
|
return ((resp_data2 == "Hello World!") and (resp_data1 == "This URI doesn't exist"))
|
||||||
|
|
||||||
|
def test_custom_uri_query(ip, port, query, verbosity = False):
|
||||||
|
verbose_print(verbosity, "======== GET HANDLER TEST =============")
|
||||||
|
# Establish HTTP connection
|
||||||
|
verbose_print(verbosity, "Connecting to => " + ip + ":" + port)
|
||||||
|
sess = http.client.HTTPConnection(ip + ":" + port, timeout = 15)
|
||||||
|
|
||||||
|
uri = "/hello?" + query
|
||||||
|
# GET hello response
|
||||||
|
verbose_print(verbosity, "Sending GET to URI : ", uri)
|
||||||
|
sess.request("GET", url=uri, headers={})
|
||||||
|
resp = sess.getresponse()
|
||||||
|
resp_data = resp.read().decode()
|
||||||
|
|
||||||
|
verbose_print(verbosity, "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv")
|
||||||
|
verbose_print(verbosity, "Server response to GET /hello")
|
||||||
|
verbose_print(verbosity, "Response Data : " + resp_data)
|
||||||
|
verbose_print(verbosity, "========================================\n")
|
||||||
|
|
||||||
|
# Close HTTP connection
|
||||||
|
sess.close()
|
||||||
|
return (resp_data == "Hello World!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Configure argument parser
|
||||||
|
parser = argparse.ArgumentParser(description='Run HTTPd Test')
|
||||||
|
parser.add_argument('IP' , metavar='IP' , type=str, help='Server IP')
|
||||||
|
parser.add_argument('port', metavar='port', type=str, help='Server port')
|
||||||
|
parser.add_argument('msg', metavar='message', type=str, help='Message to be sent to server')
|
||||||
|
args = vars(parser.parse_args())
|
||||||
|
|
||||||
|
# Get arguments
|
||||||
|
ip = args['IP']
|
||||||
|
port = args['port']
|
||||||
|
msg = args['msg']
|
||||||
|
|
||||||
|
if not test_get_handler (ip, port, True):
|
||||||
|
Utility.console_log("Failed!")
|
||||||
|
if not test_post_handler(ip, port, msg, True):
|
||||||
|
Utility.console_log("Failed!")
|
||||||
|
if not test_put_handler (ip, port, True):
|
||||||
|
Utility.console_log("Failed!")
|
Reference in New Issue
Block a user