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

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