Merge branch 'feature/http_server_err_handling' into 'master'

http_server : Add feature for invoking user configurable handlers during server errors

See merge request idf/esp-idf!4229
This commit is contained in:
Angus Gratton 2019-02-26 11:12:06 +08:00
commit f614f5bfa9
13 changed files with 645 additions and 285 deletions

View File

@ -13,4 +13,11 @@ menu "HTTP Server"
help
This sets the maximum supported size of HTTP request URI to be processed by the server
config HTTPD_ERR_RESP_NO_DELAY
bool "Use TCP_NODELAY socket option when sending HTTP error responses"
default y
help
Using TCP_NODEALY socket option ensures that HTTP error response reaches the client before the
underlying socket is closed. Please note that turning this off may cause multiple test failures
endmenu

View File

@ -471,6 +471,122 @@ esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char* uri);
* @}
*/
/* ************** Group: HTTP Error ************** */
/** @name HTTP Error
* Prototype for HTTP errors and error handling functions
* @{
*/
/**
* @brief Error codes sent as HTTP response 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_INTERNAL_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 / upgrade field 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 immediately 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 CONFIG_HTTPD_MAX_URI_LEN */
HTTPD_414_URI_TOO_LONG,
/* Headers section larger than CONFIG_HTTPD_MAX_REQ_HDR_LEN */
HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE,
/* Used internally for retrieving the total count of errors */
HTTPD_ERR_CODE_MAX
} httpd_err_code_t;
/**
* @brief Function prototype for HTTP error handling.
*
* This function is executed upon HTTP errors generated during
* internal processing of an HTTP request. This is used to override
* the default behavior on error, which is to send HTTP error response
* and close the underlying socket.
*
* @note
* - If implemented, the server will not automatically send out HTTP
* error response codes, therefore, httpd_resp_send_err() must be
* invoked inside this function if user wishes to generate HTTP
* error responses.
* - When invoked, the validity of `uri`, `method`, `content_len`
* and `user_ctx` fields of the httpd_req_t parameter is not
* guaranteed as the HTTP request may be partially received/parsed.
* - The function must return ESP_OK if underlying socket needs to
* be kept open. Any other value will ensure that the socket is
* closed. The return value is ignored when error is of type
* `HTTPD_500_INTERNAL_SERVER_ERROR` and the socket closed anyway.
*
* @param[in] req HTTP request for which the error needs to be handled
* @param[in] error Error type
*
* @return
* - ESP_OK : error handled successful
* - ESP_FAIL : failure indicates that the underlying socket needs to be closed
*/
typedef esp_err_t (*httpd_err_handler_func_t)(httpd_req_t *req,
httpd_err_code_t error);
/**
* @brief Function for registering HTTP error handlers
*
* This function maps a handler function to any supported error code
* given by `httpd_err_code_t`. See prototype `httpd_err_handler_func_t`
* above for details.
*
* @param[in] handle HTTP server handle
* @param[in] error Error type
* @param[in] handler_fn User implemented handler function
* (Pass NULL to unset any previously set handler)
*
* @return
* - ESP_OK : handler registered successfully
* - ESP_ERR_INVALID_ARG : invalid error code or server handle
*/
esp_err_t httpd_register_err_handler(httpd_handle_t handle,
httpd_err_code_t error,
httpd_err_handler_func_t handler_fn);
/** End of HTTP Error
* @}
*/
/* ************** Group: TX/RX ************** */
/** @name TX / RX
* Prototype for HTTPDs low-level send/recv functions
@ -901,7 +1017,7 @@ esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len
* - ESP_ERR_HTTPD_RESP_SEND : Error in raw send
* - ESP_ERR_HTTPD_INVALID_REQ : Invalid request
*/
inline esp_err_t httpd_resp_sendstr(httpd_req_t *r, const char *str) {
static inline esp_err_t httpd_resp_sendstr(httpd_req_t *r, const char *str) {
return httpd_resp_send(r, str, (str == NULL) ? 0 : strlen(str));
}
@ -922,7 +1038,7 @@ inline esp_err_t httpd_resp_sendstr(httpd_req_t *r, const char *str) {
* - ESP_ERR_HTTPD_RESP_SEND : Error in raw send
* - ESP_ERR_HTTPD_INVALID_REQ : Invalid request
*/
inline esp_err_t httpd_resp_sendstr_chunk(httpd_req_t *r, const char *str) {
static inline esp_err_t httpd_resp_sendstr_chunk(httpd_req_t *r, const char *str) {
return httpd_resp_send_chunk(r, str, (str == NULL) ? 0 : strlen(str));
}
@ -1014,6 +1130,30 @@ esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type);
*/
esp_err_t httpd_resp_set_hdr(httpd_req_t *r, const char *field, const char *value);
/**
* @brief For sending out error code in response to HTTP request.
*
* @note
* - This API is supposed to be called only from the context of
* a URI handler where httpd_req_t* request pointer is valid.
* - Once this API is called, all request headers are purged, so
* request headers need be copied into separate buffers if
* they are required later.
* - If you wish to send additional data in the body of the
* response, please use the lower-level functions directly.
*
* @param[in] req Pointer to the HTTP request for which the response needs to be sent
* @param[in] error Error type to send
* @param[in] msg Error message string (pass NULL for default message)
*
* @return
* - ESP_OK : On successfully sending the response packet
* - ESP_ERR_INVALID_ARG : Null arguments
* - ESP_ERR_HTTPD_RESP_SEND : Error in raw send
* - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer
*/
esp_err_t httpd_resp_send_err(httpd_req_t *req, httpd_err_code_t error, const char *msg);
/**
* @brief Helper function for HTTP 404
*
@ -1035,7 +1175,9 @@ esp_err_t httpd_resp_set_hdr(httpd_req_t *r, const char *field, const char *valu
* - ESP_ERR_HTTPD_RESP_SEND : Error in raw send
* - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer
*/
esp_err_t httpd_resp_send_404(httpd_req_t *r);
static inline esp_err_t httpd_resp_send_404(httpd_req_t *r) {
return httpd_resp_send_err(r, HTTPD_404_NOT_FOUND, NULL);
}
/**
* @brief Helper function for HTTP 408
@ -1058,7 +1200,9 @@ esp_err_t httpd_resp_send_404(httpd_req_t *r);
* - ESP_ERR_HTTPD_RESP_SEND : Error in raw send
* - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer
*/
esp_err_t httpd_resp_send_408(httpd_req_t *r);
static inline esp_err_t httpd_resp_send_408(httpd_req_t *r) {
return httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT, NULL);
}
/**
* @brief Helper function for HTTP 500
@ -1081,7 +1225,9 @@ esp_err_t httpd_resp_send_408(httpd_req_t *r);
* - ESP_ERR_HTTPD_RESP_SEND : Error in raw send
* - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer
*/
esp_err_t httpd_resp_send_500(httpd_req_t *r);
static inline esp_err_t httpd_resp_send_500(httpd_req_t *r) {
return httpd_resp_send_err(r, HTTPD_500_INTERNAL_SERVER_ERROR, NULL);
}
/**
* @brief Raw HTTP send

View File

@ -32,7 +32,7 @@ extern "C" {
/* 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 */
* exceed the scratch buffer size and should at least be 8 bytes */
#define PARSER_BLOCK_SIZE 128
/* Calculate the maximum size needed for the scratch buffer */
@ -54,64 +54,6 @@ struct thread_data {
} 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.
*/
@ -131,7 +73,7 @@ struct sock_db {
};
/**
* @brief Auxilary data structure for use during reception and processing
* @brief Auxiliary data structure for use during reception and processing
* of requests and temporarily keeping responses
*/
struct httpd_req_aux {
@ -151,7 +93,7 @@ struct httpd_req_aux {
};
/**
* @brief Server data for each instance. This is exposed publicaly as
* @brief Server data for each instance. This is exposed publicly as
* httpd_handle_t but internal structure/members are kept private.
*/
struct httpd_data {
@ -159,11 +101,14 @@ struct httpd_data {
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 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 */
/* Array of registered error handler functions */
httpd_err_handler_func_t *err_handler_fns;
};
/******************* Group : Session Management ********************/
@ -204,7 +149,7 @@ void httpd_sess_init(struct httpd_data *hd);
* @param[in] newfd Descriptor of the new client to be added to the session.
*
* @return
* - ESP_OK : on successfully queueing the work
* - ESP_OK : on successfully queuing the work
* - ESP_FAIL : in case of control socket error while sending
*/
esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd);
@ -226,7 +171,7 @@ esp_err_t httpd_sess_process(struct httpd_data *hd, int clifd);
* 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
* to continue the iteration correctly. This ensures 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.
@ -249,7 +194,7 @@ int httpd_sess_delete(struct httpd_data *hd, int clifd);
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
* @brief Add descriptors present in the socket database to an fdset and
* update the value of maxfd which are needed by the select function
* for looking through all available sockets for incoming data.
*
@ -288,12 +233,12 @@ 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
* This is needed as httpd_unrecv may un-receive next
* packet in the stream. If only partial packet was
* received then select() would mark the fd for processing
* as remaining part of the packet would still be in socket
* recv queue. But if a complete packet got unreceived
* then it would not be processed until furtur data is
* then it would not be processed until further data is
* received on the socket. This is when this function
* comes in use, as it checks the socket's pending data
* buffer.
@ -343,7 +288,7 @@ esp_err_t httpd_sess_close_lru(struct httpd_data *hd);
esp_err_t httpd_uri(struct httpd_data *hd);
/**
* @brief Deregister all URI handlers
* @brief Unregister all URI handlers
*
* @param[in] hd Server instance data
*/
@ -353,7 +298,7 @@ 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
* @param[in] req Pointer to HTTP request that needs to be validated
*
* @return
* - true : if valid request
@ -363,7 +308,7 @@ 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 */
* the check by default and enable it only if necessary */
#ifdef CONFIG_HTTPD_VALIDATE_REQ
#define httpd_valid_req(r) httpd_validate_req_ptr(r)
#else
@ -409,6 +354,19 @@ esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd);
*/
esp_err_t httpd_req_delete(struct httpd_data *hd);
/**
* @brief For handling HTTP errors by invoking registered
* error handler function
*
* @param[in] req Pointer to the HTTP request for which error occurred
* @param[in] error Error type
*
* @return
* - ESP_OK : error handled successful
* - ESP_FAIL : failure indicates that the underlying socket needs to be closed
*/
esp_err_t httpd_req_handle_err(httpd_req_t *req, httpd_err_code_t error);
/** End of Group : Parsing
* @}
*/
@ -419,22 +377,10 @@ esp_err_t httpd_req_delete(struct httpd_data *hd);
* @{
*/
/**
* @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] req Pointer to the HTTP request for which the response needs to be sent
* @param[in] buf Pointer to the buffer from where the body of the response is taken
* @param[in] buf_len Length of the buffer
*
@ -457,7 +403,7 @@ int httpd_send(httpd_req_t *req, const char *buf, size_t buf_len);
* @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
* @param[in] halt_after_pending When set true, halts immediately after receiving from
* pending buffer
*
* @return

View File

@ -288,31 +288,43 @@ 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");
if (!hd) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP server instance"));
return NULL;
}
hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
if (!hd->hd_calls) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP URI handlers"));
free(hd);
return NULL;
}
hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
if (!hd->hd_sd) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP session data"));
free(hd->hd_calls);
free(hd);
return NULL;
}
struct httpd_req_aux *ra = &hd->hd_req_aux;
ra->resp_hdrs = calloc(config->max_resp_headers, sizeof(struct resp_hdr));
if (!ra->resp_hdrs) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP response headers"));
free(hd->hd_sd);
free(hd->hd_calls);
free(hd);
return NULL;
}
hd->err_handler_fns = calloc(HTTPD_ERR_CODE_MAX, sizeof(httpd_err_handler_func_t));
if (!hd->err_handler_fns) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP error handlers"));
free(ra->resp_hdrs);
free(hd->hd_sd);
free(hd->hd_calls);
free(hd);
return NULL;
}
/* Save the configuration for this instance */
hd->config = *config;
return hd;
}
@ -320,6 +332,7 @@ static void httpd_delete(struct httpd_data *hd)
{
struct httpd_req_aux *ra = &hd->hd_req_aux;
/* Free memory of httpd instance data */
free(hd->err_handler_fns);
free(ra->resp_hdrs);
free(hd->hd_sd);

View File

@ -46,7 +46,7 @@ typedef struct {
} status;
/* Response error code in case of PARSING_FAILED */
httpd_err_resp_t error;
httpd_err_code_t error;
/* For storing last callback parameters */
struct {
@ -81,7 +81,6 @@ static esp_err_t verify_url (http_parser *parser)
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;
}
@ -128,6 +127,7 @@ static esp_err_t cb_url(http_parser *parser,
parser_data->status = PARSING_URL;
} else if (parser_data->status != PARSING_URL) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -194,6 +194,9 @@ static esp_err_t cb_header_field(http_parser *parser, const char *at, size_t len
/* Check previous status */
if (parser_data->status == PARSING_URL) {
if (verify_url(parser) != ESP_OK) {
/* verify_url would already have set the
* error field of parser data, so only setting
* status to failed */
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -207,6 +210,7 @@ static esp_err_t cb_header_field(http_parser *parser, const char *at, size_t len
/* Stop parsing for now and give control to process */
if (pause_parsing(parser, at) != ESP_OK) {
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -221,6 +225,7 @@ static esp_err_t cb_header_field(http_parser *parser, const char *at, size_t len
parser_data->status = PARSING_HDR_FIELD;
} else if (parser_data->status != PARSING_HDR_FIELD) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -251,6 +256,7 @@ static esp_err_t cb_header_value(http_parser *parser, const char *at, size_t len
ra->req_hdrs_count++;
} else if (parser_data->status != PARSING_HDR_VALUE) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -275,6 +281,9 @@ static esp_err_t cb_headers_complete(http_parser *parser)
if (parser_data->status == PARSING_URL) {
ESP_LOGD(TAG, LOG_FMT("no headers"));
if (verify_url(parser) != ESP_OK) {
/* verify_url would already have set the
* error field of parser data, so only setting
* status to failed */
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -287,6 +296,7 @@ static esp_err_t cb_headers_complete(http_parser *parser)
parser_data->last.at += parser_data->last.length;
} else {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -300,7 +310,9 @@ static esp_err_t cb_headers_complete(http_parser *parser)
if (parser->upgrade) {
ESP_LOGW(TAG, LOG_FMT("upgrade from HTTP not supported"));
parser_data->error = HTTPD_XXX_UPGRADE_NOT_SUPPORTED;
/* There is no specific HTTP error code to notify the client that
* upgrade is not supported, thus sending 400 Bad Request */
parser_data->error = HTTPD_400_BAD_REQUEST;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -320,6 +332,7 @@ static esp_err_t cb_on_body(http_parser *parser, const char *at, size_t length)
/* Check previous status */
if (parser_data->status != PARSING_BODY) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -329,6 +342,7 @@ static esp_err_t cb_on_body(http_parser *parser, const char *at, size_t length)
* may reset the parser state and cause current
* request packet to be lost */
if (pause_parsing(parser, at) != ESP_OK) {
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -352,11 +366,15 @@ static esp_err_t cb_no_body(http_parser *parser)
if (parser_data->status == PARSING_URL) {
ESP_LOGD(TAG, LOG_FMT("no headers"));
if (verify_url(parser) != ESP_OK) {
/* verify_url would already have set the
* error field of parser data, so only setting
* status to failed */
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->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -369,6 +387,7 @@ static esp_err_t cb_no_body(http_parser *parser)
* may reset the parser state and cause current
* request packet to be lost */
if (pause_parsing(parser, at) != ESP_OK) {
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
@ -396,13 +415,25 @@ static int read_block(httpd_req_t *req, size_t offset, size_t length)
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 timeout occurred allow the
* situation to be handled */
if (nbytes == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_err(req, HTTPD_408_REQ_TIMEOUT);
/* Invoke error handler which may return ESP_OK
* to signal for retrying call to recv(), else it may
* return ESP_FAIL to signal for closure of socket */
return (httpd_req_handle_err(req, HTTPD_408_REQ_TIMEOUT) == ESP_OK) ?
HTTPD_SOCK_ERR_TIMEOUT : HTTPD_SOCK_ERR_FAIL;
}
return -1;
/* Some socket error occurred. Return failure
* to force closure of underlying socket.
* Error message is not sent as socket may not
* be valid anymore */
return HTTPD_SOCK_ERR_FAIL;
} else if (nbytes == 0) {
ESP_LOGD(TAG, LOG_FMT("connection closed"));
return -1;
/* Connection closed by client so no
* need to send error response */
return HTTPD_SOCK_ERR_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("received HTTP request block size = %d"), nbytes);
@ -417,7 +448,11 @@ static int parse_block(http_parser *parser, size_t offset, size_t length)
size_t nparsed = 0;
if (!length) {
ESP_LOGW(TAG, LOG_FMT("response uri/header too big"));
/* Parsing is still happening but nothing to
* parse means no more space left on buffer,
* therefore it can be inferred that the
* request URI/header must be too long */
ESP_LOGW(TAG, LOG_FMT("request URI/header too long"));
switch (data->status) {
case PARSING_URL:
data->error = HTTPD_414_URI_TOO_LONG;
@ -425,14 +460,17 @@ static int parse_block(http_parser *parser, size_t offset, size_t length)
case PARSING_HDR_FIELD:
case PARSING_HDR_VALUE:
data->error = HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE;
break;
default:
ESP_LOGE(TAG, LOG_FMT("unexpected state"));
data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
break;
}
data->status = PARSING_FAILED;
return -1;
}
/* Unpause the parsing if paused */
/* Un-pause the parsing if paused */
if (data->paused) {
nparsed = continue_parsing(parser, length);
length -= nparsed;
@ -448,6 +486,8 @@ static int parse_block(http_parser *parser, size_t offset, size_t length)
/* Check state */
if (data->status == PARSING_FAILED) {
/* It is expected that the error field of
* parser data should have been set by now */
ESP_LOGW(TAG, LOG_FMT("parsing failed"));
return -1;
} else if (data->paused) {
@ -457,8 +497,8 @@ static int parse_block(http_parser *parser, size_t offset, size_t length)
return 0;
} else if (nparsed != length) {
/* http_parser error */
data->status = PARSING_FAILED;
data->error = HTTPD_400_BAD_REQUEST;
data->status = PARSING_FAILED;
ESP_LOGW(TAG, LOG_FMT("incomplete (%d/%d) with parser error = %d"),
nparsed, length, parser->http_errno);
return -1;
@ -508,7 +548,16 @@ static esp_err_t httpd_parse_req(struct httpd_data *hd)
do {
/* Read block into scratch buffer */
if ((blk_len = read_block(r, offset, PARSER_BLOCK_SIZE)) < 0) {
/* Return error to close socket */
if (blk_len == HTTPD_SOCK_ERR_TIMEOUT) {
/* Retry read in case of non-fatal timeout error.
* read_block() ensures that the timeout error is
* handled properly so that this doesn't get stuck
* in an infinite loop */
continue;
}
/* If not HTTPD_SOCK_ERR_TIMEOUT, returned error must
* be HTTPD_SOCK_ERR_FAIL which means we need to return
* failure and thereby close the underlying socket */
return ESP_FAIL;
}
@ -518,8 +567,10 @@ static esp_err_t httpd_parse_req(struct httpd_data *hd)
/* 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);
/* HTTP error occurred.
* Send error code as response status and
* invoke error handler */
return httpd_req_handle_err(r, parser_data.error);
}
} while (parser_data.status != PARSING_COMPLETE);

View File

@ -379,78 +379,128 @@ esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len
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)
esp_err_t httpd_resp_send_err(httpd_req_t *req, httpd_err_code_t error, const char *usr_msg)
{
esp_err_t ret;
const char *msg;
const char *status;
switch (error) {
case HTTPD_501_METHOD_NOT_IMPLEMENTED:
status = "501 Method Not Implemented";
msg = "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";
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 does not 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_500_INTERNAL_SERVER_ERROR:
default:
status = "500 Internal Server Error";
msg = "Server has encountered an unexpected error";
}
/* If user has provided custom message, override default message */
msg = usr_msg ? usr_msg : msg;
ESP_LOGW(TAG, LOG_FMT("%s - %s"), status, msg);
httpd_resp_set_status (req, status);
httpd_resp_set_type (req, HTTPD_TYPE_TEXT);
return httpd_resp_send (req, msg, strlen(msg));
/* Set error code in HTTP response */
httpd_resp_set_status(req, status);
httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
#ifdef CONFIG_HTTPD_ERR_RESP_NO_DELAY
/* Use TCP_NODELAY option to force socket to send data in buffer
* This ensures that the error message is sent before the socket
* is closed */
struct httpd_req_aux *ra = req->aux;
int nodelay = 1;
if (setsockopt(ra->sd->fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)) < 0) {
/* If failed to turn on TCP_NODELAY, throw warning and continue */
ESP_LOGW(TAG, LOG_FMT("error calling setsockopt : %d"), errno);
nodelay = 0;
}
#endif
/* Send HTTP error message */
ret = httpd_resp_send(req, msg, strlen(msg));
#ifdef CONFIG_HTTPD_ERR_RESP_NO_DELAY
/* If TCP_NODELAY was set successfully above, time to disable it */
if (nodelay == 1) {
nodelay = 0;
if (setsockopt(ra->sd->fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)) < 0) {
/* If failed to turn off TCP_NODELAY, throw error and
* return failure to signal for socket closure */
ESP_LOGE(TAG, LOG_FMT("error calling setsockopt : %d"), errno);
return ESP_ERR_INVALID_STATE;
}
}
#endif
return ret;
}
esp_err_t httpd_register_err_handler(httpd_handle_t handle,
httpd_err_code_t error,
httpd_err_handler_func_t err_handler_fn)
{
if (handle == NULL || error >= HTTPD_ERR_CODE_MAX) {
return ESP_ERR_INVALID_ARG;
}
struct httpd_data *hd = (struct httpd_data *) handle;
hd->err_handler_fns[error] = err_handler_fn;
return ESP_OK;
}
esp_err_t httpd_req_handle_err(httpd_req_t *req, httpd_err_code_t error)
{
struct httpd_data *hd = (struct httpd_data *) req->handle;
esp_err_t ret;
/* Invoke custom error handler if configured */
if (hd->err_handler_fns[error]) {
ret = hd->err_handler_fns[error](req, error);
/* If error code is 500, force return failure
* irrespective of the handler's return value */
ret = (error == HTTPD_500_INTERNAL_SERVER_ERROR ? ESP_FAIL : ret);
} else {
/* If no handler is registered for this error default
* behavior is to send the HTTP error response and
* return failure for closure of underlying socket */
httpd_resp_send_err(req, error, NULL);
ret = ESP_FAIL;
}
return ret;
}
int httpd_req_recv(httpd_req_t *r, char *buf, size_t buf_len)

View File

@ -93,7 +93,7 @@ bool httpd_uri_match_wildcard(const char *template, const char *uri, size_t len)
static httpd_uri_t* httpd_find_uri_handler(struct httpd_data *hd,
const char *uri, size_t uri_len,
httpd_method_t method,
httpd_err_resp_t *err)
httpd_err_code_t *err)
{
if (err) {
*err = HTTPD_404_NOT_FOUND;
@ -279,7 +279,7 @@ esp_err_t httpd_uri(struct httpd_data *hd)
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;
httpd_err_code_t err = 0;
ESP_LOGD(TAG, LOG_FMT("request for %s with type %d"), req->uri, req->method);
@ -294,11 +294,11 @@ esp_err_t httpd_uri(struct httpd_data *hd)
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);
return httpd_req_handle_err(req, HTTPD_404_NOT_FOUND);
case HTTPD_405_METHOD_NOT_ALLOWED:
ESP_LOGW(TAG, LOG_FMT("Method '%d' not allowed for URI '%s'"),
req->method, req->uri);
return httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED);
return httpd_req_handle_err(req, HTTPD_405_METHOD_NOT_ALLOWED);
default:
return ESP_FAIL;
}

View File

@ -95,6 +95,7 @@ typedef struct httpd_ssl_config httpd_ssl_config_t;
.global_transport_ctx_free_fn = NULL, \
.open_fn = NULL, \
.close_fn = NULL, \
.uri_match_fn = NULL \
}, \
.cacert_pem = NULL, \
.cacert_len = 0, \

View File

@ -55,6 +55,7 @@ esp_err_t echo_post_handler(httpd_req_t *req)
int ret;
if (!buf) {
ESP_LOGE(TAG, "Failed to allocate memory of %d bytes!", req->content_len + 1);
httpd_resp_send_500(req);
return ESP_FAIL;
}
@ -84,12 +85,15 @@ esp_err_t echo_post_handler(httpd_req_t *req)
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);
if (!req_hdr) {
ESP_LOGE(TAG, "Failed to allocate memory of %d bytes!", hdr_len + 1);
httpd_resp_send_500(req);
return ESP_FAIL;
}
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);

View File

@ -367,7 +367,7 @@ def put_hello(dut, port):
def post_hello(dut, port):
# POST /hello returns 405'
Utility.console_log("[test] POST /hello returns 404 =>", end=' ')
Utility.console_log("[test] POST /hello returns 405 =>", end=' ')
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
conn.request("POST", "/hello", "Hello")
resp = conn.getresponse()
@ -541,8 +541,10 @@ def leftover_data_test(dut, port):
if not test_val("False URI Status", str(404), str(resp.status)):
s.close()
return False
resp.read()
# socket would have been closed by server due to error
s.close()
s = http.client.HTTPConnection(dut + ":" + port, timeout=15)
s.request("GET", url='/hello')
resp = s.getresponse()
if not test_val("Hello World Data", "Hello World!", resp.read().decode()):
@ -637,7 +639,7 @@ 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
content_len = 2**30
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()
s.read_resp_data()
@ -802,7 +804,7 @@ def send_postx_hdr_len(dut, port, length):
hdr = s.read_resp_hdrs()
resp = s.read_resp_data()
s.close()
if "Custom" in hdr:
if hdr and ("Custom" in hdr):
return (hdr["Custom"] == custom_hdr_val), resp
return False, s.status
@ -826,7 +828,7 @@ def test_upgrade_not_supported(dut, port):
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()
s.read_resp_data()
if not test_val("Client Error", "200", s.status):
if not test_val("Client Error", "400", s.status):
s.close()
return False
s.close()

View File

@ -72,9 +72,10 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req)
const size_t entrypath_offset = strlen(fullpath);
if (!dir) {
/* If opening directory failed then send 404 server error */
httpd_resp_send_404(req);
return ESP_OK;
ESP_LOGE(TAG, "Failed to stat dir : %s", fullpath);
/* Respond with 404 Not Found */
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Directory does not exist");
return ESP_FAIL;
}
/* Send HTML file header */
@ -172,18 +173,17 @@ static esp_err_t http_resp_file(httpd_req_t *req)
strcat(filepath, req->uri);
if (stat(filepath, &file_stat) == -1) {
ESP_LOGE(TAG, "Failed to stat file : %s", filepath);
/* If file doesn't exist respond with 404 Not Found */
httpd_resp_send_404(req);
return ESP_OK;
/* Respond with 404 Not Found */
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
return ESP_FAIL;
}
fd = fopen(filepath, "r");
if (!fd) {
ESP_LOGE(TAG, "Failed to read existing file : %s", filepath);
/* If file exists but unable to open respond with 500 Server Error */
httpd_resp_set_status(req, "500 Server Error");
httpd_resp_sendstr(req, "Failed to read existing file!");
return ESP_OK;
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Sending file : %s (%ld bytes)...", filepath, file_stat.st_size);
@ -202,10 +202,9 @@ static esp_err_t http_resp_file(httpd_req_t *req)
ESP_LOGE(TAG, "File sending failed!");
/* Abort sending file */
httpd_resp_sendstr_chunk(req, NULL);
/* Send error message with status code */
httpd_resp_set_status(req, "500 Server Error");
httpd_resp_sendstr(req, "Failed to send file!");
return ESP_OK;
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
return ESP_FAIL;
}
/* Keep looping till the whole file is sent */
@ -249,10 +248,8 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
if (strlen(filename) == 0 || filename[strlen(filename) - 1] == '/') {
ESP_LOGE(TAG, "Invalid file name : %s", filename);
/* Respond with 400 Bad Request */
httpd_resp_set_status(req, "400 Bad Request");
/* Send failure reason */
httpd_resp_sendstr(req, "Invalid file name!");
return ESP_OK;
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid file name");
return ESP_FAIL;
}
/* Retrieve the base path of file storage to construct the full path */
@ -262,18 +259,18 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
strcat(filepath, filename);
if (stat(filepath, &file_stat) == 0) {
ESP_LOGE(TAG, "File already exists : %s", filepath);
/* If file exists respond with 400 Bad Request */
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_sendstr(req, "File already exists!");
return ESP_OK;
/* Respond with 400 Bad Request */
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File already exists");
return ESP_FAIL;
}
/* File cannot be larger than a limit */
if (req->content_len > MAX_FILE_SIZE) {
ESP_LOGE(TAG, "File too large : %d bytes", req->content_len);
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_sendstr(req, "File size must be less than "
MAX_FILE_SIZE_STR "!");
/* Respond with 400 Bad Request */
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"File size must be less than "
MAX_FILE_SIZE_STR "!");
/* Return failure to close underlying connection else the
* incoming file content will keep the socket busy */
return ESP_FAIL;
@ -282,10 +279,9 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
fd = fopen(filepath, "w");
if (!fd) {
ESP_LOGE(TAG, "Failed to create file : %s", filepath);
/* If file creation failed, respond with 500 Server Error */
httpd_resp_set_status(req, "500 Server Error");
httpd_resp_sendstr(req, "Failed to create file!");
return ESP_OK;
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Receiving file : %s...", filename);
@ -314,10 +310,9 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
unlink(filepath);
ESP_LOGE(TAG, "File reception failed!");
/* Return failure reason with status code */
httpd_resp_set_status(req, "500 Server Error");
httpd_resp_sendstr(req, "Failed to receive file!");
return ESP_OK;
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file");
return ESP_FAIL;
}
/* Write buffer content to file on storage */
@ -328,9 +323,9 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
unlink(filepath);
ESP_LOGE(TAG, "File write failed!");
httpd_resp_set_status(req, "500 Server Error");
httpd_resp_sendstr(req, "Failed to write file to storage!");
return ESP_OK;
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to write file to storage");
return ESP_FAIL;
}
/* Keep track of remaining size of
@ -363,10 +358,8 @@ static esp_err_t delete_post_handler(httpd_req_t *req)
if (strlen(filename) == 0 || filename[strlen(filename) - 1] == '/') {
ESP_LOGE(TAG, "Invalid file name : %s", filename);
/* Respond with 400 Bad Request */
httpd_resp_set_status(req, "400 Bad Request");
/* Send failure reason */
httpd_resp_sendstr(req, "Invalid file name!");
return ESP_OK;
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid file name");
return ESP_FAIL;
}
/* Retrieve the base path of file storage to construct the full path */
@ -376,10 +369,9 @@ static esp_err_t delete_post_handler(httpd_req_t *req)
strcat(filepath, filename);
if (stat(filepath, &file_stat) == -1) {
ESP_LOGE(TAG, "File does not exist : %s", filename);
/* If file does not exist respond with 400 Bad Request */
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_sendstr(req, "File does not exist!");
return ESP_OK;
/* Respond with 400 Bad Request */
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File does not exist");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Deleting file : %s", filename);

View File

@ -152,6 +152,33 @@ httpd_uri_t echo = {
.user_ctx = NULL
};
/* This handler allows the custom error handling functionality to be
* tested from client side. For that, when a PUT request 0 is sent to
* URI /ctrl, the /hello and /echo URIs are unregistered and following
* custom error handler http_404_error_handler() is registered.
* Afterwards, when /hello or /echo is requested, this custom error
* handler is invoked which, after sending an error message to client,
* either closes the underlying socket (when requested URI is /echo)
* or keeps it open (when requested URI is /hello). This allows the
* client to infer if the custom error handler is functioning as expected
* by observing the socket state.
*/
esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err)
{
if (strcmp("/hello", req->uri) == 0) {
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "/hello URI is not available");
/* Return ESP_OK to keep underlying socket open */
return ESP_OK;
} else if (strcmp("/echo", req->uri) == 0) {
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "/echo URI is not available");
/* Return ESP_FAIL to close underlying socket */
return ESP_FAIL;
}
/* For any other URI send 404 and close socket */
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Some 404 error message");
return ESP_FAIL;
}
/* An HTTP PUT handler. This demonstrates realtime
* registration and deregistration of URI handlers
*/
@ -168,15 +195,19 @@ esp_err_t ctrl_put_handler(httpd_req_t *req)
}
if (buf == '0') {
/* Handler can be unregistered using the uri string */
/* URI handlers 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");
/* Register the custom error handler */
httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, http_404_error_handler);
}
else {
ESP_LOGI(TAG, "Registering /hello and /echo URIs");
httpd_register_uri_handler(req->handle, &hello);
httpd_register_uri_handler(req->handle, &echo);
/* Unregister custom error handler */
httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, NULL);
}
/* Respond with empty body */

View File

@ -19,7 +19,20 @@ from __future__ import unicode_literals
from builtins import str
import http.client
import argparse
import Utility
try:
import Utility
except ImportError:
import sys
import os
# This environment variable is expected on the host machine
# > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
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)
import Utility
def verbose_print(verbosity, *args):
@ -27,6 +40,16 @@ def verbose_print(verbosity, *args):
Utility.console_log(''.join(str(elems) for elems in args))
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
def test_get_handler(ip, port, verbosity=False):
verbose_print(verbosity, "======== GET HANDLER TEST =============")
# Establish HTTP connection
@ -44,12 +67,15 @@ def test_get_handler(ip, port, verbosity=False):
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 Exception:
# Close HTTP connection
sess.close()
if not (
test_val("Status code mismatch", 200, resp.status) and
test_val("Response mismatch", "Custom-Value-1", resp.getheader("Custom-Header-1")) and
test_val("Response mismatch", "Custom-Value-2", resp.getheader("Custom-Header-2")) and
test_val("Response mismatch", "Hello World!", resp_data)
):
return False
verbose_print(verbosity, "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv")
@ -59,10 +85,7 @@ def test_get_handler(ip, port, verbosity=False):
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!")
return True
def test_post_handler(ip, port, msg, verbosity=False):
@ -82,7 +105,7 @@ def test_post_handler(ip, port, msg, verbosity=False):
# Close HTTP connection
sess.close()
return (resp_data == msg)
return test_val("Response mismatch", msg, resp_data)
def test_put_handler(ip, port, verbosity=False):
@ -91,31 +114,125 @@ def test_put_handler(ip, port, verbosity=False):
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")
# PUT message to /ctrl to disable /hello and /echo URI handlers
# and set 404 error handler to custom http_404_error_handler()
verbose_print(verbosity, "Disabling /hello and /echo handlers")
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)
try:
# Send HTTP request to /hello URI
sess.request("GET", url="/hello")
resp = sess.getresponse()
resp_data = resp.read().decode()
# 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()
# 404 Error must be returned from server as URI /hello is no longer available.
# But the custom error handler http_404_error_handler() will not close the
# session if the requested URI is /hello
if not test_val("Status code mismatch", 404, resp.status):
raise AssertionError
sess.request("GET", url="/hello")
resp = sess.getresponse()
resp_data2 = resp.read().decode()
verbose_print(verbosity, "Response on GET /hello : " + resp_data2)
# Compare error response string with expectation
verbose_print(verbosity, "Response on GET /hello : " + resp_data)
if not test_val("Response mismatch", "/hello URI is not available", resp_data):
raise AssertionError
# Close HTTP connection
sess.close()
return ((resp_data2 == "Hello World!") and (resp_data1 == "This URI doesn't exist"))
# Using same session for sending an HTTP request to /echo, as it is expected
# that the custom error handler http_404_error_handler() would not have closed
# the session
sess.request("POST", url="/echo", body="Some content")
resp = sess.getresponse()
resp_data = resp.read().decode()
# 404 Error must be returned from server as URI /hello is no longer available.
# The custom error handler http_404_error_handler() will close the session
# this time as the requested URI is /echo
if not test_val("Status code mismatch", 404, resp.status):
raise AssertionError
# Compare error response string with expectation
verbose_print(verbosity, "Response on POST /echo : " + resp_data)
if not test_val("Response mismatch", "/echo URI is not available", resp_data):
raise AssertionError
try:
# Using same session should fail as by now the session would have closed
sess.request("POST", url="/hello", body="Some content")
resp = sess.getresponse()
resp.read().decode()
# If control reaches this point then the socket was not closed.
# This is not expected
verbose_print(verbosity, "Socket not closed by server")
raise AssertionError
except http.client.HTTPException:
# Catch socket error as we tried to communicate with an already closed socket
pass
except http.client.HTTPException:
verbose_print(verbosity, "Socket closed by server")
return False
except AssertionError:
return False
finally:
# Close HTTP connection
sess.close()
verbose_print(verbosity, "Enabling /hello handler")
# Create new connection
sess = http.client.HTTPConnection(ip + ":" + port, timeout=15)
# PUT message to /ctrl to enable /hello URI handler
# and restore 404 error handler to default
sess.request("PUT", url="/ctrl", body="1")
resp = sess.getresponse()
resp.read()
# Close HTTP connection
sess.close()
# Create new connection
sess = http.client.HTTPConnection(ip + ":" + port, timeout=15)
try:
# Sending HTTP request to /hello should work now
sess.request("GET", url="/hello")
resp = sess.getresponse()
resp_data = resp.read().decode()
if not test_val("Status code mismatch", 200, resp.status):
raise AssertionError
verbose_print(verbosity, "Response on GET /hello : " + resp_data)
if not test_val("Response mismatch", "Hello World!", resp_data):
raise AssertionError
# 404 Error handler should have been restored to default
sess.request("GET", url="/invalid")
resp = sess.getresponse()
resp_data = resp.read().decode()
if not test_val("Status code mismatch", 404, resp.status):
raise AssertionError
verbose_print(verbosity, "Response on GET /invalid : " + resp_data)
if not test_val("Response mismatch", "This URI does not exist", resp_data):
raise AssertionError
except http.client.HTTPException:
verbose_print(verbosity, "Socket closed by server")
return False
except AssertionError:
return False
finally:
# Close HTTP connection
sess.close()
return True
def test_custom_uri_query(ip, port, query, verbosity=False):
@ -138,7 +255,7 @@ def test_custom_uri_query(ip, port, query, verbosity=False):
# Close HTTP connection
sess.close()
return (resp_data == "Hello World!")
return "Hello World!" == resp_data
if __name__ == '__main__':
@ -154,9 +271,9 @@ if __name__ == '__main__':
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):
if not (
test_get_handler(ip, port, True) and
test_put_handler(ip, port, True) and
test_post_handler(ip, port, msg, True)
):
Utility.console_log("Failed!")