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

Added APIs :
  * httpd_resp_send_err()        : for sending HTTP error responses for error codes given by httpd_err_code_t. It uses TCP_NODELAY option to ensure that HTTP error responses reach the client before socket is closed.
  * httpd_register_err_handler() : for registering HTTP error handler functions of type httpd_err_handler_func_t.

The default behavior, on encountering errors during processing of HTTP requests, is now to send HTTP error response (if possible) and close the underlying socket. User configurable handlers can be used to override this behavior for each error individually (except for 500 Internal Server Error).

Also fixed some typos.

Closes https://github.com/espressif/esp-idf/issues/3005
This commit is contained in:
Anurag Kar 2019-02-01 17:47:41 +05:30 committed by bot
parent 5ec58c316d
commit 28412d8cb6
7 changed files with 407 additions and 194 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;
}