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

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

View File

@ -0,0 +1,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()

View 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

View File

@ -0,0 +1,4 @@
COMPONENT_SRCDIRS := src src/util
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_PRIV_INCLUDEDIRS := src/port/esp32 src/util

File diff suppressed because it is too large Load Diff

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

View File

@ -0,0 +1,531 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _HTTPD_PRIV_H_
#define _HTTPD_PRIV_H_
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_http_server.h>
#include "osal.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Size of request data block/chunk (not to be confused with chunked encoded data)
* that is received and parsed in one turn of the parsing process. This should not
* exceed the scratch buffer size and should atleast be 8 bytes */
#define PARSER_BLOCK_SIZE 128
/* Calculate the maximum size needed for the scratch buffer */
#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN)
/* Formats a log string to prepend context function name */
#define LOG_FMT(x) "%s: " x, __func__
/**
* @brief Thread related data for internal use
*/
struct thread_data {
othread_t handle; /*!< Handle to thread/task */
enum {
THREAD_IDLE = 0,
THREAD_RUNNING,
THREAD_STOPPING,
THREAD_STOPPED,
} status; /*!< State of the thread */
};
/**
* @brief Error codes sent by server in case of errors
* encountered during processing of an HTTP request
*/
typedef enum {
/* For any unexpected errors during parsing, like unexpected
* state transitions, or unhandled errors.
*/
HTTPD_500_SERVER_ERROR = 0,
/* For methods not supported by http_parser. Presently
* http_parser halts parsing when such methods are
* encountered and so the server responds with 400 Bad
* Request error instead.
*/
HTTPD_501_METHOD_NOT_IMPLEMENTED,
/* When HTTP version is not 1.1 */
HTTPD_505_VERSION_NOT_SUPPORTED,
/* Returned when http_parser halts parsing due to incorrect
* syntax of request, unsupported method in request URI or
* due to chunked encoding option present in headers
*/
HTTPD_400_BAD_REQUEST,
/* When requested URI is not found */
HTTPD_404_NOT_FOUND,
/* When URI found, but method has no handler registered */
HTTPD_405_METHOD_NOT_ALLOWED,
/* Intended for recv timeout. Presently it's being sent
* for other recv errors as well. Client should expect the
* server to immediatly close the connection after
* responding with this.
*/
HTTPD_408_REQ_TIMEOUT,
/* Intended for responding to chunked encoding, which is
* not supported currently. Though unhandled http_parser
* callback for chunked request returns "400 Bad Request"
*/
HTTPD_411_LENGTH_REQUIRED,
/* URI length greater than HTTPD_MAX_URI_LEN */
HTTPD_414_URI_TOO_LONG,
/* Headers section larger thn HTTPD_MAX_REQ_HDR_LEN */
HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE,
/* There is no particular HTTP error code for not supporting
* upgrade. For this respond with 200 OK. Client expects status
* code 101 if upgrade were supported, so 200 should be fine.
*/
HTTPD_XXX_UPGRADE_NOT_SUPPORTED
} httpd_err_resp_t;
/**
* @brief A database of all the open sockets in the system.
*/
struct sock_db {
int fd; /*!< The file descriptor for this socket */
void *ctx; /*!< A custom context for this socket */
void *transport_ctx; /*!< A custom 'transport' context for this socket, to be used by send/recv/pending */
httpd_handle_t handle; /*!< Server handle */
httpd_free_ctx_fn_t free_ctx; /*!< Function for freeing the context */
httpd_free_ctx_fn_t free_transport_ctx; /*!< Function for freeing the 'transport' context */
httpd_send_func_t send_fn; /*!< Send function for this socket */
httpd_recv_func_t recv_fn; /*!< Receive function for this socket */
httpd_pending_func_t pending_fn; /*!< Pending function for this socket */
int64_t timestamp; /*!< Timestamp indicating when the socket was last used */
char pending_data[PARSER_BLOCK_SIZE]; /*!< Buffer for pending data to be received */
size_t pending_len; /*!< Length of pending data to be received */
};
/**
* @brief Auxilary data structure for use during reception and processing
* of requests and temporarily keeping responses
*/
struct httpd_req_aux {
struct sock_db *sd; /*!< Pointer to socket database */
char scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */
size_t remaining_len; /*!< Amount of data remaining to be fetched */
char *status; /*!< HTTP response's status code */
char *content_type; /*!< HTTP response's content type */
bool first_chunk_sent; /*!< Used to indicate if first chunk sent */
unsigned req_hdrs_count; /*!< Count of total headers in request packet */
unsigned resp_hdrs_count; /*!< Count of additional headers in response packet */
struct resp_hdr {
const char *field;
const char *value;
} *resp_hdrs; /*!< Additional headers in response packet */
struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */
};
/**
* @brief Server data for each instance. This is exposed publicaly as
* httpd_handle_t but internal structure/members are kept private.
*/
struct httpd_data {
httpd_config_t config; /*!< HTTPD server configuration */
int listen_fd; /*!< Server listener FD */
int ctrl_fd; /*!< Ctrl message receiver FD */
int msg_fd; /*!< Ctrl message sender FD */
struct thread_data hd_td; /*!< Information for the HTTPd thread */
struct sock_db *hd_sd; /*!< The socket database */
httpd_uri_t **hd_calls; /*!< Registered URI handlers */
struct httpd_req hd_req; /*!< The current HTTPD request */
struct httpd_req_aux hd_req_aux; /*!< Additional data about the HTTPD request kept unexposed */
};
/******************* Group : Session Management ********************/
/** @name Session Management
* Functions related to HTTP session management
* @{
*/
/**
* @brief Retrieve a session by its descriptor
*
* @param[in] hd Server instance data
* @param[in] sockfd Socket FD
* @return pointer into the socket DB, or NULL if not found
*/
struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd);
/**
* @brief Delete sessions whose FDs have became invalid.
* This is a recovery strategy e.g. after select() fails.
*
* @param[in] hd Server instance data
*/
void httpd_sess_delete_invalid(struct httpd_data *hd);
/**
* @brief Initializes an http session by resetting the sockets database.
*
* @param[in] hd Server instance data
*/
void httpd_sess_init(struct httpd_data *hd);
/**
* @brief Starts a new session for client requesting connection and adds
* it's descriptor to the socket database.
*
* @param[in] hd Server instance data
* @param[in] newfd Descriptor of the new client to be added to the session.
*
* @return
* - ESP_OK : on successfully queueing the work
* - ESP_FAIL : in case of control socket error while sending
*/
esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd);
/**
* @brief Processes incoming HTTP requests
*
* @param[in] hd Server instance data
* @param[in] clifd Descriptor of the client from which data is to be received
*
* @return
* - ESP_OK : on successfully receiving, parsing and responding to a request
* - ESP_FAIL : in case of failure in any of the stages of processing
*/
esp_err_t httpd_sess_process(struct httpd_data *hd, int clifd);
/**
* @brief Remove client descriptor from the session / socket database
* and close the connection for this client.
*
* @note The returned descriptor should be used by httpd_sess_iterate()
* to continue the iteration correctly. This ensurs that the
* iteration is not restarted abruptly which may cause reading from
* a socket which has been already processed and thus blocking
* the server loop until data appears on that socket.
*
* @param[in] hd Server instance data
* @param[in] clifd Descriptor of the client to be removed from the session.
*
* @return
* - +VE : Client descriptor preceding the one being deleted
* - -1 : No descriptor preceding the one being deleted
*/
int httpd_sess_delete(struct httpd_data *hd, int clifd);
/**
* @brief Free session context
*
* @param[in] ctx Pointer to session context
* @param[in] free_fn Free function to call on session context
*/
void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn);
/**
* @brief Add descriptors present in the socket database to an fd_set and
* update the value of maxfd which are needed by the select function
* for looking through all available sockets for incoming data.
*
* @param[in] hd Server instance data
* @param[out] fdset File descriptor set to be updated.
* @param[out] maxfd Maximum value among all file descriptors.
*/
void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd);
/**
* @brief Iterates through the list of client fds in the session /socket database.
* Passing the value of a client fd returns the fd for the next client
* in the database. In order to iterate from the beginning pass -1 as fd.
*
* @param[in] hd Server instance data
* @param[in] fd Last accessed client descriptor.
* -1 to reset iterator to start of database.
*
* @return
* - +VE : Client descriptor next in the database
* - -1 : End of iteration
*/
int httpd_sess_iterate(struct httpd_data *hd, int fd);
/**
* @brief Checks if session can accept another connection from new client.
* If sockets database is full then this returns false.
*
* @param[in] hd Server instance data
*
* @return True if session can accept new clients
*/
bool httpd_is_sess_available(struct httpd_data *hd);
/**
* @brief Checks if session has any pending data/packets
* for processing
*
* This is needed as httpd_unrecv may unreceive next
* packet in the stream. If only partial packet was
* received then select() would mark the fd for processing
* as remaining part of the packet would still be in socket
* recv queue. But if a complete packet got unreceived
* then it would not be processed until furtur data is
* received on the socket. This is when this function
* comes in use, as it checks the socket's pending data
* buffer.
*
* @param[in] hd Server instance data
* @param[in] fd Client descriptor
*
* @return True if there is any pending data
*/
bool httpd_sess_pending(struct httpd_data *hd, int fd);
/**
* @brief Removes the least recently used client from the session
*
* This may be useful if new clients are requesting for connection but
* max number of connections is reached, in which case the client which
* is inactive for the longest will be removed from the session.
*
* @param[in] hd Server instance data
*
* @return
* - ESP_OK : if session closure initiated successfully
* - ESP_FAIL : if failed
*/
esp_err_t httpd_sess_close_lru(struct httpd_data *hd);
/** End of Group : Session Management
* @}
*/
/****************** Group : URI Handling ********************/
/** @name URI Handling
* Methods for accessing URI handlers
* @{
*/
/**
* @brief For an HTTP request, searches through all the registered URI handlers
* and invokes the appropriate one if found
*
* @param[in] hd Server instance data for which handler needs to be invoked
*
* @return
* - ESP_OK : if handler found and executed successfully
* - ESP_FAIL : otherwise
*/
esp_err_t httpd_uri(struct httpd_data *hd);
/**
* @brief Deregister all URI handlers
*
* @param[in] hd Server instance data
*/
void httpd_unregister_all_uri_handlers(struct httpd_data *hd);
/**
* @brief Validates the request to prevent users from calling APIs, that are to
* be called only inside a URI handler, outside the handler context
*
* @param[in] req Pointer to HTTP request that neds to be validated
*
* @return
* - true : if valid request
* - false : otherwise
*/
bool httpd_validate_req_ptr(httpd_req_t *r);
/* httpd_validate_req_ptr() adds some overhead to frequently used APIs,
* and is useful mostly for debugging, so it's preferable to disable
* the check by defaut and enable it only if necessary */
#ifdef CONFIG_HTTPD_VALIDATE_REQ
#define httpd_valid_req(r) httpd_validate_req_ptr(r)
#else
#define httpd_valid_req(r) true
#endif
/** End of Group : URI Handling
* @}
*/
/****************** Group : Processing ********************/
/** @name Processing
* Methods for processing HTTP requests
* @{
*/
/**
* @brief Initiates the processing of HTTP request
*
* Receives incoming TCP packet on a socket, then parses the packet as
* HTTP request and fills httpd_req_t data structure with the extracted
* URI, headers are ready to be fetched from scratch buffer and calling
* http_recv() after this reads the body of the request.
*
* @param[in] hd Server instance data
* @param[in] sd Pointer to socket which is needed for receiving TCP packets.
*
* @return
* - ESP_OK : if request packet is valid
* - ESP_FAIL : otherwise
*/
esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd);
/**
* @brief For an HTTP request, resets the resources allocated for it and
* purges any data left to be received
*
* @param[in] hd Server instance data
*
* @return
* - ESP_OK : if request packet deleted and resources cleaned.
* - ESP_FAIL : otherwise.
*/
esp_err_t httpd_req_delete(struct httpd_data *hd);
/** End of Group : Parsing
* @}
*/
/****************** Group : Send/Receive ********************/
/** @name Send and Receive
* Methods for transmitting and receiving HTTP requests and responses
* @{
*/
/**
* @brief For sending out error code in response to HTTP request.
*
* @param[in] req Pointer to the HTTP request for which the resonse needs to be sent
* @param[in] error Error type to send
*
* @return
* - ESP_OK : if successful
* - ESP_FAIL : if failed
*/
esp_err_t httpd_resp_send_err(httpd_req_t *req, httpd_err_resp_t error);
/**
* @brief For sending out data in response to an HTTP request.
*
* @param[in] req Pointer to the HTTP request for which the resonse needs to be sent
* @param[in] buf Pointer to the buffer from where the body of the response is taken
* @param[in] buf_len Length of the buffer
*
* @return
* - Length of data : if successful
* - ESP_FAIL : if failed
*/
int httpd_send(httpd_req_t *req, const char *buf, size_t buf_len);
/**
* @brief For receiving HTTP request data
*
* @note The exposed API httpd_recv() is simply this function with last parameter
* set as false. This function is used internally during reception and
* processing of a new request. The option to halt after receiving pending
* data prevents the server from requesting more data than is needed for
* completing a packet in case when all the remaining part of the packet is
* in the pending buffer.
*
* @param[in] req Pointer to new HTTP request which only has the socket descriptor
* @param[out] buf Pointer to the buffer which will be filled with the received data
* @param[in] buf_len Length of the buffer
* @param[in] halt_after_pending When set true, halts immediatly after receiving from
* pending buffer
*
* @return
* - Length of data : if successful
* - ESP_FAIL : if failed
*/
int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t buf_len, bool halt_after_pending);
/**
* @brief For un-receiving HTTP request data
*
* This function copies data into internal buffer pending_data so that
* when httpd_recv is called, it first fetches this pending data and
* then only starts receiving from the socket
*
* @note If data is too large for the internal buffer then only
* part of the data is unreceived, reflected in the returned
* length. Make sure that such truncation is checked for and
* handled properly.
*
* @param[in] req Pointer to new HTTP request which only has the socket descriptor
* @param[in] buf Pointer to the buffer from where data needs to be un-received
* @param[in] buf_len Length of the buffer
*
* @return Length of data copied into pending buffer
*/
size_t httpd_unrecv(struct httpd_req *r, const char *buf, size_t buf_len);
/**
* @brief This is the low level default send function of the HTTPD. This should
* NEVER be called directly. The semantics of this is exactly similar to
* send() of the BSD socket API.
*
* @param[in] hd Server instance data
* @param[in] sockfd Socket descriptor for sending data
* @param[in] buf Pointer to the buffer from where the body of the response is taken
* @param[in] buf_len Length of the buffer
* @param[in] flags Flags for mode selection
*
* @return
* - Length of data : if successful
* - -1 : if failed (appropriate errno is set)
*/
int httpd_default_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags);
/**
* @brief This is the low level default recv function of the HTTPD. This should
* NEVER be called directly. The semantics of this is exactly similar to
* recv() of the BSD socket API.
*
* @param[in] hd Server instance data
* @param[in] sockfd Socket descriptor for sending data
* @param[out] buf Pointer to the buffer which will be filled with the received data
* @param[in] buf_len Length of the buffer
* @param[in] flags Flags for mode selection
*
* @return
* - Length of data : if successful
* - -1 : if failed (appropriate errno is set)
*/
int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags);
/** End of Group : Send and Receive
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ! _HTTPD_PRIV_H_ */

View File

@ -0,0 +1,403 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <errno.h>
#include <esp_log.h>
#include <esp_err.h>
#include <assert.h>
#include <esp_http_server.h>
#include "esp_httpd_priv.h"
#include "ctrl_sock.h"
static const char *TAG = "httpd";
static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd)
{
/* If no space is available for new session, close the least recently used one */
if (hd->config.lru_purge_enable == true) {
if (!httpd_is_sess_available(hd)) {
/* Queue asynchronous closure of the least recently used session */
return httpd_sess_close_lru(hd);
/* Returning from this allowes the main server thread to process
* the queued asynchronous control message for closing LRU session.
* Since connection request hasn't been addressed yet using accept()
* therefore httpd_accept_conn() will be called again, but this time
* with space available for one session
*/
}
}
struct sockaddr_in addr_from;
socklen_t addr_from_len = sizeof(addr_from);
int new_fd = accept(listen_fd, (struct sockaddr *)&addr_from, &addr_from_len);
if (new_fd < 0) {
ESP_LOGW(TAG, LOG_FMT("error in accept (%d)"), errno);
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd);
struct timeval tv;
/* Set recv timeout of this fd as per config */
tv.tv_sec = hd->config.recv_wait_timeout;
tv.tv_usec = 0;
setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
/* Set send timeout of this fd as per config */
tv.tv_sec = hd->config.send_wait_timeout;
tv.tv_usec = 0;
setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
if (ESP_OK != httpd_sess_new(hd, new_fd)) {
ESP_LOGW(TAG, LOG_FMT("session creation failed"));
close(new_fd);
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("complete"));
return ESP_OK;
}
struct httpd_ctrl_data {
enum httpd_ctrl_msg {
HTTPD_CTRL_SHUTDOWN,
HTTPD_CTRL_WORK,
} hc_msg;
httpd_work_fn_t hc_work;
void *hc_work_arg;
};
esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *arg)
{
if (handle == NULL || work == NULL) {
return ESP_ERR_INVALID_ARG;
}
struct httpd_data *hd = (struct httpd_data *) handle;
struct httpd_ctrl_data msg = {
.hc_msg = HTTPD_CTRL_WORK,
.hc_work = work,
.hc_work_arg = arg,
};
int ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
if (ret < 0) {
ESP_LOGW(TAG, LOG_FMT("failed to queue work"));
return ESP_FAIL;
}
return ESP_OK;
}
void *httpd_get_global_user_ctx(httpd_handle_t handle)
{
return ((struct httpd_data *)handle)->config.global_user_ctx;
}
void *httpd_get_global_transport_ctx(httpd_handle_t handle)
{
return ((struct httpd_data *)handle)->config.global_transport_ctx;
}
static void httpd_close_all_sessions(struct httpd_data *hd)
{
int fd = -1;
while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), fd);
httpd_sess_delete(hd, fd);
close(fd);
}
}
static void httpd_process_ctrl_msg(struct httpd_data *hd)
{
struct httpd_ctrl_data msg;
int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);
if (ret <= 0) {
ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);
return;
}
if (ret != sizeof(msg)) {
ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
return;
}
switch (msg.hc_msg) {
case HTTPD_CTRL_WORK:
if (msg.hc_work) {
ESP_LOGD(TAG, LOG_FMT("work"));
(*msg.hc_work)(msg.hc_work_arg);
}
break;
case HTTPD_CTRL_SHUTDOWN:
ESP_LOGD(TAG, LOG_FMT("shutdown"));
hd->hd_td.status = THREAD_STOPPING;
break;
default:
break;
}
}
/* Manage in-coming connection or data requests */
static esp_err_t httpd_server(struct httpd_data *hd)
{
fd_set read_set;
FD_ZERO(&read_set);
FD_SET(hd->listen_fd, &read_set);
FD_SET(hd->ctrl_fd, &read_set);
int tmp_max_fd;
httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);
int maxfd = MAX(hd->listen_fd, tmp_max_fd);
tmp_max_fd = maxfd;
maxfd = MAX(hd->ctrl_fd, tmp_max_fd);
ESP_LOGD(TAG, LOG_FMT("doing select maxfd+1 = %d"), maxfd + 1);
int active_cnt = select(maxfd + 1, &read_set, NULL, NULL, NULL);
if (active_cnt < 0) {
ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno);
httpd_sess_delete_invalid(hd);
return ESP_OK;
}
/* Case0: Do we have a control message? */
if (FD_ISSET(hd->ctrl_fd, &read_set)) {
ESP_LOGD(TAG, LOG_FMT("processing ctrl message"));
httpd_process_ctrl_msg(hd);
if (hd->hd_td.status == THREAD_STOPPING) {
ESP_LOGD(TAG, LOG_FMT("stopping thread"));
return ESP_FAIL;
}
}
/* Case1: Do we have any activity on the current data
* sessions? */
int fd = -1;
while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) {
ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
if (httpd_sess_process(hd, fd) != ESP_OK) {
ESP_LOGD(TAG, LOG_FMT("closing socket %d"), fd);
close(fd);
/* Delete session and update fd to that
* preceding the one being deleted */
fd = httpd_sess_delete(hd, fd);
}
}
}
/* Case2: Do we have any incoming connection requests to
* process? */
if (FD_ISSET(hd->listen_fd, &read_set)) {
ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd);
if (httpd_accept_conn(hd, hd->listen_fd) != ESP_OK) {
ESP_LOGW(TAG, LOG_FMT("error accepting new connection"));
}
}
return ESP_OK;
}
/* The main HTTPD thread */
static void httpd_thread(void *arg)
{
int ret;
struct httpd_data *hd = (struct httpd_data *) arg;
hd->hd_td.status = THREAD_RUNNING;
ESP_LOGD(TAG, LOG_FMT("web server started"));
while (1) {
ret = httpd_server(hd);
if (ret != ESP_OK) {
break;
}
}
ESP_LOGD(TAG, LOG_FMT("web server exiting"));
close(hd->msg_fd);
cs_free_ctrl_sock(hd->ctrl_fd);
httpd_close_all_sessions(hd);
close(hd->listen_fd);
hd->hd_td.status = THREAD_STOPPED;
httpd_os_thread_delete();
}
static esp_err_t httpd_server_init(struct httpd_data *hd)
{
int fd = socket(PF_INET6, SOCK_STREAM, 0);
if (fd < 0) {
ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
return ESP_FAIL;
}
struct in6_addr inaddr_any = IN6ADDR_ANY_INIT;
struct sockaddr_in6 serv_addr = {
.sin6_family = PF_INET6,
.sin6_addr = inaddr_any,
.sin6_port = htons(hd->config.server_port)
};
int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (ret < 0) {
ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
close(fd);
return ESP_FAIL;
}
ret = listen(fd, hd->config.backlog_conn);
if (ret < 0) {
ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);
close(fd);
return ESP_FAIL;
}
int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);
if (ctrl_fd < 0) {
ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);
close(fd);
return ESP_FAIL;
}
int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (msg_fd < 0) {
ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);
close(fd);
close(ctrl_fd);
return ESP_FAIL;
}
hd->listen_fd = fd;
hd->ctrl_fd = ctrl_fd;
hd->msg_fd = msg_fd;
return ESP_OK;
}
static struct httpd_data *httpd_create(const httpd_config_t *config)
{
/* Allocate memory for httpd instance data */
struct httpd_data *hd = calloc(1, sizeof(struct httpd_data));
if (hd != NULL) {
hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
if (hd->hd_calls == NULL) {
free(hd);
return NULL;
}
hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
if (hd->hd_sd == NULL) {
free(hd->hd_calls);
free(hd);
return NULL;
}
struct httpd_req_aux *ra = &hd->hd_req_aux;
ra->resp_hdrs = calloc(config->max_resp_headers, sizeof(struct resp_hdr));
if (ra->resp_hdrs == NULL) {
free(hd->hd_sd);
free(hd->hd_calls);
free(hd);
return NULL;
}
/* Save the configuration for this instance */
hd->config = *config;
} else {
ESP_LOGE(TAG, "mem alloc failed");
}
return hd;
}
static void httpd_delete(struct httpd_data *hd)
{
struct httpd_req_aux *ra = &hd->hd_req_aux;
/* Free memory of httpd instance data */
free(ra->resp_hdrs);
free(hd->hd_sd);
/* Free registered URI handlers */
httpd_unregister_all_uri_handlers(hd);
free(hd->hd_calls);
free(hd);
}
esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
{
if (handle == NULL || config == NULL) {
return ESP_ERR_INVALID_ARG;
}
struct httpd_data *hd = httpd_create(config);
if (hd == NULL) {
/* Failed to allocate memory */
return ESP_ERR_HTTPD_ALLOC_MEM;
}
if (httpd_server_init(hd) != ESP_OK) {
httpd_delete(hd);
return ESP_FAIL;
}
httpd_sess_init(hd);
if (httpd_os_thread_create(&hd->hd_td.handle, "httpd",
hd->config.stack_size,
hd->config.task_priority,
httpd_thread, hd) != ESP_OK) {
/* Failed to launch task */
httpd_delete(hd);
return ESP_ERR_HTTPD_TASK;
}
*handle = (httpd_handle_t *)hd;
return ESP_OK;
}
esp_err_t httpd_stop(httpd_handle_t handle)
{
struct httpd_data *hd = (struct httpd_data *) handle;
if (hd == NULL) {
return ESP_ERR_INVALID_ARG;
}
struct httpd_ctrl_data msg;
memset(&msg, 0, sizeof(msg));
msg.hc_msg = HTTPD_CTRL_SHUTDOWN;
cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
ESP_LOGD(TAG, LOG_FMT("sent control msg to stop server"));
while (hd->hd_td.status != THREAD_STOPPED) {
httpd_os_thread_sleep(100);
}
/* Release global user context, if not NULL */
if (hd->config.global_user_ctx) {
if (hd->config.global_user_ctx_free_fn) {
hd->config.global_user_ctx_free_fn(hd->config.global_user_ctx);
} else {
free(hd->config.global_user_ctx);
}
hd->config.global_user_ctx = NULL;
}
/* Release global transport context, if not NULL */
if (hd->config.global_transport_ctx) {
if (hd->config.global_transport_ctx_free_fn) {
hd->config.global_transport_ctx_free_fn(hd->config.global_transport_ctx);
} else {
free(hd->config.global_transport_ctx);
}
hd->config.global_transport_ctx = NULL;
}
ESP_LOGD(TAG, LOG_FMT("server stopped"));
httpd_delete(hd);
return ESP_OK;
}

View File

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

View File

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

View File

@ -0,0 +1,553 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_http_server.h>
#include "esp_httpd_priv.h"
static const char *TAG = "httpd_txrx";
esp_err_t httpd_sess_set_send_override(httpd_handle_t hd, int sockfd, httpd_send_func_t send_func)
{
struct sock_db *sess = httpd_sess_get(hd, sockfd);
if (!sess) {
return ESP_ERR_INVALID_ARG;
}
sess->send_fn = send_func;
return ESP_OK;
}
esp_err_t httpd_sess_set_recv_override(httpd_handle_t hd, int sockfd, httpd_recv_func_t recv_func)
{
struct sock_db *sess = httpd_sess_get(hd, sockfd);
if (!sess) {
return ESP_ERR_INVALID_ARG;
}
sess->recv_fn = recv_func;
return ESP_OK;
}
esp_err_t httpd_sess_set_pending_override(httpd_handle_t hd, int sockfd, httpd_pending_func_t pending_func)
{
struct sock_db *sess = httpd_sess_get(hd, sockfd);
if (!sess) {
return ESP_ERR_INVALID_ARG;
}
sess->pending_fn = pending_func;
return ESP_OK;
}
int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len)
{
if (r == NULL || buf == NULL) {
return HTTPD_SOCK_ERR_INVALID;
}
if (!httpd_valid_req(r)) {
return HTTPD_SOCK_ERR_INVALID;
}
struct httpd_req_aux *ra = r->aux;
int ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0);
if (ret < 0) {
ESP_LOGD(TAG, LOG_FMT("error in send_fn"));
return ret;
}
return ret;
}
static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len)
{
struct httpd_req_aux *ra = r->aux;
int ret;
while (buf_len > 0) {
ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0);
if (ret < 0) {
ESP_LOGD(TAG, LOG_FMT("error in send_fn"));
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("sent = %d"), ret);
buf += ret;
buf_len -= ret;
}
return ESP_OK;
}
static size_t httpd_recv_pending(httpd_req_t *r, char *buf, size_t buf_len)
{
struct httpd_req_aux *ra = r->aux;
size_t offset = sizeof(ra->sd->pending_data) - ra->sd->pending_len;
/* buf_len must not be greater than remaining_len */
buf_len = MIN(ra->sd->pending_len, buf_len);
memcpy(buf, ra->sd->pending_data + offset, buf_len);
ra->sd->pending_len -= buf_len;
return buf_len;
}
int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t buf_len, bool halt_after_pending)
{
ESP_LOGD(TAG, LOG_FMT("requested length = %d"), buf_len);
size_t pending_len = 0;
struct httpd_req_aux *ra = r->aux;
/* First fetch pending data from local buffer */
if (ra->sd->pending_len > 0) {
ESP_LOGD(TAG, LOG_FMT("pending length = %d"), ra->sd->pending_len);
pending_len = httpd_recv_pending(r, buf, buf_len);
buf += pending_len;
buf_len -= pending_len;
/* If buffer filled then no need to recv.
* If asked to halt after receiving pending data then
* return with received length */
if (!buf_len || halt_after_pending) {
return pending_len;
}
}
/* Receive data of remaining length */
int ret = ra->sd->recv_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0);
if (ret < 0) {
ESP_LOGD(TAG, LOG_FMT("error in recv_fn"));
if ((ret == HTTPD_SOCK_ERR_TIMEOUT) && (pending_len != 0)) {
/* If recv() timeout occurred, but pending data is
* present, return length of pending data.
* This behavior is similar to that of socket recv()
* function, which, in case has only partially read the
* requested length, due to timeout, returns with read
* length, rather than error */
return pending_len;
}
return ret;
}
ESP_LOGD(TAG, LOG_FMT("received length = %d"), ret + pending_len);
return ret + pending_len;
}
int httpd_recv(httpd_req_t *r, char *buf, size_t buf_len)
{
return httpd_recv_with_opt(r, buf, buf_len, false);
}
size_t httpd_unrecv(struct httpd_req *r, const char *buf, size_t buf_len)
{
struct httpd_req_aux *ra = r->aux;
/* Truncate if external buf_len is greater than pending_data buffer size */
ra->sd->pending_len = MIN(sizeof(ra->sd->pending_data), buf_len);
/* Copy data into internal pending_data buffer */
size_t offset = sizeof(ra->sd->pending_data) - ra->sd->pending_len;
memcpy(ra->sd->pending_data + offset, buf, buf_len);
ESP_LOGD(TAG, LOG_FMT("length = %d"), ra->sd->pending_len);
return ra->sd->pending_len;
}
/**
* This API appends an additional header field-value pair in the HTTP response.
* But the header isn't sent out until any of the send APIs is executed.
*/
esp_err_t httpd_resp_set_hdr(httpd_req_t *r, const char *field, const char *value)
{
if (r == NULL || field == NULL || value == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!httpd_valid_req(r)) {
return ESP_ERR_HTTPD_INVALID_REQ;
}
struct httpd_req_aux *ra = r->aux;
struct httpd_data *hd = (struct httpd_data *) r->handle;
/* Number of additional headers is limited */
if (ra->resp_hdrs_count >= hd->config.max_resp_headers) {
return ESP_ERR_HTTPD_RESP_HDR;
}
/* Assign header field-value pair */
ra->resp_hdrs[ra->resp_hdrs_count].field = field;
ra->resp_hdrs[ra->resp_hdrs_count].value = value;
ra->resp_hdrs_count++;
ESP_LOGD(TAG, LOG_FMT("new header = %s: %s"), field, value);
return ESP_OK;
}
/**
* This API sets the status of the HTTP response to the value specified.
* But the status isn't sent out until any of the send APIs is executed.
*/
esp_err_t httpd_resp_set_status(httpd_req_t *r, const char *status)
{
if (r == NULL || status == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!httpd_valid_req(r)) {
return ESP_ERR_HTTPD_INVALID_REQ;
}
struct httpd_req_aux *ra = r->aux;
ra->status = (char *)status;
return ESP_OK;
}
/**
* This API sets the method/type of the HTTP response to the value specified.
* But the method isn't sent out until any of the send APIs is executed.
*/
esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type)
{
if (r == NULL || type == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!httpd_valid_req(r)) {
return ESP_ERR_HTTPD_INVALID_REQ;
}
struct httpd_req_aux *ra = r->aux;
ra->content_type = (char *)type;
return ESP_OK;
}
esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len)
{
if (r == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!httpd_valid_req(r)) {
return ESP_ERR_HTTPD_INVALID_REQ;
}
struct httpd_req_aux *ra = r->aux;
const char *httpd_hdr_str = "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n";
const char *colon_separator = ": ";
const char *cr_lf_seperator = "\r\n";
if (buf_len == -1) buf_len = strlen(buf);
/* Request headers are no longer available */
ra->req_hdrs_count = 0;
/* Size of essential headers is limited by scratch buffer size */
if (snprintf(ra->scratch, sizeof(ra->scratch), httpd_hdr_str,
ra->status, ra->content_type, buf_len) >= sizeof(ra->scratch)) {
return ESP_ERR_HTTPD_RESP_HDR;
}
/* Sending essential headers */
if (httpd_send_all(r, ra->scratch, strlen(ra->scratch)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
/* Sending additional headers based on set_header */
for (unsigned i = 0; i < ra->resp_hdrs_count; i++) {
/* Send header field */
if (httpd_send_all(r, ra->resp_hdrs[i].field, strlen(ra->resp_hdrs[i].field)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
/* Send ': ' */
if (httpd_send_all(r, colon_separator, strlen(colon_separator)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
/* Send header value */
if (httpd_send_all(r, ra->resp_hdrs[i].value, strlen(ra->resp_hdrs[i].value)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
/* Send CR + LF */
if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
}
/* End header section */
if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
/* Sending content */
if (buf && buf_len) {
if (httpd_send_all(r, buf, buf_len) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
}
return ESP_OK;
}
esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len)
{
if (r == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!httpd_valid_req(r)) {
return ESP_ERR_HTTPD_INVALID_REQ;
}
if (buf_len == -1) buf_len = strlen(buf);
struct httpd_req_aux *ra = r->aux;
const char *httpd_chunked_hdr_str = "HTTP/1.1 %s\r\nContent-Type: %s\r\nTransfer-Encoding: chunked\r\n";
const char *colon_separator = ": ";
const char *cr_lf_seperator = "\r\n";
/* Request headers are no longer available */
ra->req_hdrs_count = 0;
if (!ra->first_chunk_sent) {
/* Size of essential headers is limited by scratch buffer size */
if (snprintf(ra->scratch, sizeof(ra->scratch), httpd_chunked_hdr_str,
ra->status, ra->content_type) >= sizeof(ra->scratch)) {
return ESP_ERR_HTTPD_RESP_HDR;
}
/* Sending essential headers */
if (httpd_send_all(r, ra->scratch, strlen(ra->scratch)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
/* Sending additional headers based on set_header */
for (unsigned i = 0; i < ra->resp_hdrs_count; i++) {
/* Send header field */
if (httpd_send_all(r, ra->resp_hdrs[i].field, strlen(ra->resp_hdrs[i].field)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
/* Send ': ' */
if (httpd_send_all(r, colon_separator, strlen(colon_separator)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
/* Send header value */
if (httpd_send_all(r, ra->resp_hdrs[i].value, strlen(ra->resp_hdrs[i].value)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
/* Send CR + LF */
if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
}
/* End header section */
if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
ra->first_chunk_sent = true;
}
/* Sending chunked content */
char len_str[10];
snprintf(len_str, sizeof(len_str), "%x\r\n", buf_len);
if (httpd_send_all(r, len_str, strlen(len_str)) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
if (buf) {
if (httpd_send_all(r, buf, (size_t) buf_len) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
}
/* Indicate end of chunk */
if (httpd_send_all(r, "\r\n", strlen("\r\n")) != ESP_OK) {
return ESP_ERR_HTTPD_RESP_SEND;
}
return ESP_OK;
}
esp_err_t httpd_resp_send_404(httpd_req_t *r)
{
return httpd_resp_send_err(r, HTTPD_404_NOT_FOUND);
}
esp_err_t httpd_resp_send_408(httpd_req_t *r)
{
return httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT);
}
esp_err_t httpd_resp_send_500(httpd_req_t *r)
{
return httpd_resp_send_err(r, HTTPD_500_SERVER_ERROR);
}
esp_err_t httpd_resp_send_err(httpd_req_t *req, httpd_err_resp_t error)
{
const char *msg;
const char *status;
switch (error) {
case HTTPD_501_METHOD_NOT_IMPLEMENTED:
status = "501 Method Not Implemented";
msg = "Request method is not supported by server";
break;
case HTTPD_505_VERSION_NOT_SUPPORTED:
status = "505 Version Not Supported";
msg = "HTTP version not supported by server";
break;
case HTTPD_400_BAD_REQUEST:
status = "400 Bad Request";
msg = "Server unable to understand request due to invalid syntax";
break;
case HTTPD_404_NOT_FOUND:
status = "404 Not Found";
msg = "This URI doesn't exist";
break;
case HTTPD_405_METHOD_NOT_ALLOWED:
status = "405 Method Not Allowed";
msg = "Request method for this URI is not handled by server";
break;
case HTTPD_408_REQ_TIMEOUT:
status = "408 Request Timeout";
msg = "Server closed this connection";
break;
case HTTPD_414_URI_TOO_LONG:
status = "414 URI Too Long";
msg = "URI is too long for server to interpret";
break;
case HTTPD_411_LENGTH_REQUIRED:
status = "411 Length Required";
msg = "Chunked encoding not supported by server";
break;
case HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE:
status = "431 Request Header Fields Too Large";
msg = "Header fields are too long for server to interpret";
break;
case HTTPD_XXX_UPGRADE_NOT_SUPPORTED:
/* If the server does not support upgrade, or is unable to upgrade
* it responds with a standard HTTP/1.1 response */
status = "200 OK";
msg = "Upgrade not supported by server";
break;
case HTTPD_500_SERVER_ERROR:
default:
status = "500 Server Error";
msg = "Server has encountered an unexpected error";
}
ESP_LOGW(TAG, LOG_FMT("%s - %s"), status, msg);
httpd_resp_set_status (req, status);
httpd_resp_set_type (req, HTTPD_TYPE_TEXT);
return httpd_resp_send (req, msg, strlen(msg));
}
int httpd_req_recv(httpd_req_t *r, char *buf, size_t buf_len)
{
if (r == NULL || buf == NULL) {
return HTTPD_SOCK_ERR_INVALID;
}
if (!httpd_valid_req(r)) {
ESP_LOGW(TAG, LOG_FMT("invalid request"));
return HTTPD_SOCK_ERR_INVALID;
}
struct httpd_req_aux *ra = r->aux;
ESP_LOGD(TAG, LOG_FMT("remaining length = %d"), ra->remaining_len);
if (buf_len > ra->remaining_len) {
buf_len = ra->remaining_len;
}
if (buf_len == 0) {
return buf_len;
}
int ret = httpd_recv(r, buf, buf_len);
if (ret < 0) {
ESP_LOGD(TAG, LOG_FMT("error in httpd_recv"));
return ret;
}
ra->remaining_len -= ret;
ESP_LOGD(TAG, LOG_FMT("received length = %d"), ret);
return ret;
}
int httpd_req_to_sockfd(httpd_req_t *r)
{
if (r == NULL) {
return -1;
}
if (!httpd_valid_req(r)) {
ESP_LOGW(TAG, LOG_FMT("invalid request"));
return -1;
}
struct httpd_req_aux *ra = r->aux;
return ra->sd->fd;
}
static int httpd_sock_err(const char *ctx, int sockfd)
{
int errval;
int sock_err;
size_t sock_err_len = sizeof(sock_err);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sock_err, &sock_err_len) < 0) {
ESP_LOGE(TAG, LOG_FMT("error calling getsockopt : %d"), errno);
return HTTPD_SOCK_ERR_FAIL;
}
ESP_LOGW(TAG, LOG_FMT("error in %s : %d"), ctx, sock_err);
switch(sock_err) {
case EAGAIN:
case EINTR:
errval = HTTPD_SOCK_ERR_TIMEOUT;
break;
case EINVAL:
case EBADF:
case EFAULT:
case ENOTSOCK:
errval = HTTPD_SOCK_ERR_INVALID;
break;
default:
errval = HTTPD_SOCK_ERR_FAIL;
}
return errval;
}
int httpd_default_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags)
{
(void)hd;
if (buf == NULL) {
return HTTPD_SOCK_ERR_INVALID;
}
int ret = send(sockfd, buf, buf_len, flags);
if (ret < 0) {
return httpd_sock_err("send", sockfd);
}
return ret;
}
int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags)
{
(void)hd;
if (buf == NULL) {
return HTTPD_SOCK_ERR_INVALID;
}
int ret = recv(sockfd, buf, buf_len, flags);
if (ret < 0) {
return httpd_sock_err("recv", sockfd);
}
return ret;
}

View File

@ -0,0 +1,224 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <esp_log.h>
#include <esp_err.h>
#include <http_parser.h>
#include <esp_http_server.h>
#include "esp_httpd_priv.h"
static const char *TAG = "httpd_uri";
static int httpd_find_uri_handler(struct httpd_data *hd,
const char* uri,
httpd_method_t method)
{
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
if (hd->hd_calls[i]) {
ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri);
if ((hd->hd_calls[i]->method == method) && // First match methods
(strcmp(hd->hd_calls[i]->uri, uri) == 0)) { // Then match uri strings
return i;
}
}
}
return -1;
}
esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
const httpd_uri_t *uri_handler)
{
if (handle == NULL || uri_handler == NULL) {
return ESP_ERR_INVALID_ARG;
}
struct httpd_data *hd = (struct httpd_data *) handle;
/* Make sure another handler with same URI and method
* is not already registered
*/
if (httpd_find_uri_handler(handle, uri_handler->uri,
uri_handler->method) != -1) {
ESP_LOGW(TAG, LOG_FMT("handler %s with method %d already registered"),
uri_handler->uri, uri_handler->method);
return ESP_ERR_HTTPD_HANDLER_EXISTS;
}
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
if (hd->hd_calls[i] == NULL) {
hd->hd_calls[i] = malloc(sizeof(httpd_uri_t));
if (hd->hd_calls[i] == NULL) {
/* Failed to allocate memory */
return ESP_ERR_HTTPD_ALLOC_MEM;
}
/* Copy URI string */
hd->hd_calls[i]->uri = strdup(uri_handler->uri);
if (hd->hd_calls[i]->uri == NULL) {
/* Failed to allocate memory */
free(hd->hd_calls[i]);
return ESP_ERR_HTTPD_ALLOC_MEM;
}
/* Copy remaining members */
hd->hd_calls[i]->method = uri_handler->method;
hd->hd_calls[i]->handler = uri_handler->handler;
hd->hd_calls[i]->user_ctx = uri_handler->user_ctx;
ESP_LOGD(TAG, LOG_FMT("[%d] installed %s"), i, uri_handler->uri);
return ESP_OK;
}
ESP_LOGD(TAG, LOG_FMT("[%d] exists %s"), i, hd->hd_calls[i]->uri);
}
ESP_LOGW(TAG, LOG_FMT("no slots left for registering handler"));
return ESP_ERR_HTTPD_HANDLERS_FULL;
}
esp_err_t httpd_unregister_uri_handler(httpd_handle_t handle,
const char *uri, httpd_method_t method)
{
if (handle == NULL || uri == NULL) {
return ESP_ERR_INVALID_ARG;
}
struct httpd_data *hd = (struct httpd_data *) handle;
int i = httpd_find_uri_handler(hd, uri, method);
if (i != -1) {
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
free((char*)hd->hd_calls[i]->uri);
free(hd->hd_calls[i]);
hd->hd_calls[i] = NULL;
return ESP_OK;
}
ESP_LOGW(TAG, LOG_FMT("handler %s with method %d not found"), uri, method);
return ESP_ERR_NOT_FOUND;
}
esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char *uri)
{
if (handle == NULL || uri == NULL) {
return ESP_ERR_INVALID_ARG;
}
struct httpd_data *hd = (struct httpd_data *) handle;
bool found = false;
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
if ((hd->hd_calls[i] != NULL) &&
(strcmp(hd->hd_calls[i]->uri, uri) == 0)) {
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, uri);
free((char*)hd->hd_calls[i]->uri);
free(hd->hd_calls[i]);
hd->hd_calls[i] = NULL;
found = true;
}
}
if (!found) {
ESP_LOGW(TAG, LOG_FMT("no handler found for URI %s"), uri);
}
return (found ? ESP_OK : ESP_ERR_NOT_FOUND);
}
void httpd_unregister_all_uri_handlers(struct httpd_data *hd)
{
for (unsigned i = 0; i < hd->config.max_uri_handlers; i++) {
if (hd->hd_calls[i]) {
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
free((char*)hd->hd_calls[i]->uri);
free(hd->hd_calls[i]);
}
}
}
/* Alternate implmentation of httpd_find_uri_handler()
* which takes a uri_len field. This is useful when the URI
* string contains extra parameters that are not to be included
* while matching with the registered URI_handler strings
*/
static httpd_uri_t* httpd_find_uri_handler2(httpd_err_resp_t *err,
struct httpd_data *hd,
const char *uri, size_t uri_len,
httpd_method_t method)
{
*err = 0;
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
if (hd->hd_calls[i]) {
ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri);
if ((strlen(hd->hd_calls[i]->uri) == uri_len) && // First match uri length
(strncmp(hd->hd_calls[i]->uri, uri, uri_len) == 0)) { // Then match uri strings
if (hd->hd_calls[i]->method == method) { // Finally match methods
return hd->hd_calls[i];
}
/* URI found but method not allowed.
* If URI IS found later then this
* error is to be neglected */
*err = HTTPD_405_METHOD_NOT_ALLOWED;
}
}
}
if (*err == 0) {
*err = HTTPD_404_NOT_FOUND;
}
return NULL;
}
esp_err_t httpd_uri(struct httpd_data *hd)
{
httpd_uri_t *uri = NULL;
httpd_req_t *req = &hd->hd_req;
struct http_parser_url *res = &hd->hd_req_aux.url_parse_res;
/* For conveying URI not found/method not allowed */
httpd_err_resp_t err = 0;
ESP_LOGD(TAG, LOG_FMT("request for %s with type %d"), req->uri, req->method);
/* URL parser result contains offset and length of path string */
if (res->field_set & (1 << UF_PATH)) {
uri = httpd_find_uri_handler2(&err, hd,
req->uri + res->field_data[UF_PATH].off,
res->field_data[UF_PATH].len,
req->method);
}
/* If URI with method not found, respond with error code */
if (uri == NULL) {
switch (err) {
case HTTPD_404_NOT_FOUND:
ESP_LOGW(TAG, LOG_FMT("URI '%s' not found"), req->uri);
return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND);
case HTTPD_405_METHOD_NOT_ALLOWED:
ESP_LOGW(TAG, LOG_FMT("Method '%d' not allowed for URI '%s'"), req->method, req->uri);
return httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED);
default:
return ESP_FAIL;
}
}
/* Attach user context data (passed during URI registration) into request */
req->user_ctx = uri->user_ctx;
/* Invoke handler */
if (uri->handler(req) != ESP_OK) {
/* Handler returns error, this socket should be closed */
ESP_LOGW(TAG, LOG_FMT("uri handler execution failed"));
return ESP_FAIL;
}
return ESP_OK;
}

View File

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

View File

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

View File

@ -0,0 +1,99 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* \file ctrl_sock.h
* \brief Control Socket for select() wakeup
*
* LWIP doesn't allow an easy mechanism to on-demand wakeup a thread
* sleeping on select. This is a common requirement for sending
* control commands to a network server. This control socket API
* facilitates the same.
*/
#ifndef _CTRL_SOCK_H_
#define _CTRL_SOCK_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Create a control socket
*
* LWIP doesn't allow an easy mechanism to on-demand wakeup a thread
* sleeping on select. This is a common requirement for sending
* control commands to a network server. This control socket API
* facilitates the same.
*
* This API will create a UDP control socket on the specified port. It
* will return a socket descriptor that can then be added to your
* fd_set in select()
*
* @param[in] port the local port on which the control socket will listen
*
* @return - the socket descriptor that can be added to the fd_set in select.
* - an error code if less than zero
*/
int cs_create_ctrl_sock(int port);
/**
* @brief Free the control socket
*
* This frees up the control socket that was earlier created using
* cs_create_ctrl_sock()
*
* @param[in] fd the socket descriptor associated with this control socket
*/
void cs_free_ctrl_sock(int fd);
/**
* @brief Send data to control socket
*
* This API sends data to the control socket. If a server is blocked
* on select() with the control socket, this call will wake up that
* server.
*
* @param[in] send_fd the socket for sending ctrl messages
* @param[in] port the port on which the control socket was created
* @param[in] data pointer to a buffer that contains data to send on the socket
* @param[in] data_len the length of the data contained in the buffer pointed to be data
*
* @return - the number of bytes sent to the control socket
* - an error code if less than zero
*/
int cs_send_to_ctrl_sock(int send_fd, int port, void *data, unsigned int data_len);
/**
* @brief Receive data from control socket
*
* This API receives any data that was sent to the control
* socket. This will be typically called from the server thread to
* process any commands on this socket.
*
* @param[in] fd the socket descriptor of the control socket
* @param[in] data pointer to a buffer that will be used to store
* received from the control socket
* @param[in] data_len the maximum length of the data that can be
* stored in the buffer pointed by data
*
* @return - the number of bytes received from the control socket
* - an error code if less than zero
*/
int cs_recv_from_ctrl_sock(int fd, void *data, unsigned int data_len);
#ifdef __cplusplus
}
#endif
#endif /* ! _CTRL_SOCK_H_ */

View File

@ -0,0 +1,6 @@
set(COMPONENT_SRCDIRS ".")
set(COMPONENT_ADD_INCLUDEDIRS ".")
set(COMPONENT_REQUIRES unity esp_http_server)
register_component()

View File

@ -0,0 +1 @@
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

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

View File

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

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := tests
include $(IDF_PATH)/make/project.mk

View File

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

View File

@ -0,0 +1,5 @@
set(COMPONENT_SRCS "main.c"
"tests.c")
set(COMPONENT_ADD_INCLUDEDIRS ". include")
register_component()

View File

@ -0,0 +1,16 @@
menu "Example Configuration"
config WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
Can be left blank if the network has no security set.
endmenu

View File

@ -0,0 +1,5 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

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

View 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();
}

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

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

View File

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

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := persistent_sockets
include $(IDF_PATH)/make/project.mk

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

View File

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

View File

@ -0,0 +1,4 @@
set(COMPONENT_SRCS "main.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,16 @@
menu "Example Configuration"
config WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
Can be left blank if the network has no security set.
endmenu

View File

@ -0,0 +1,5 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

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

View File

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

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(simple)

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := simple
include $(IDF_PATH)/make/project.mk

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

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

View File

@ -0,0 +1,4 @@
set(COMPONENT_SRCS "main.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,16 @@
menu "Example Configuration"
config WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
Can be left blank if the network has no security set.
endmenu

View File

@ -0,0 +1,5 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

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

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