Merge branch 'feature/httpd_sock_err' into 'master'

HTTP Server : Return HTTPD_SOCK_ERR_ based on errno set during send / recv

See merge request idf/esp-idf!3445
This commit is contained in:
Angus Gratton 2018-10-18 06:59:47 +08:00
commit ca74afd5ca
9 changed files with 198 additions and 48 deletions

View file

@ -348,19 +348,40 @@ esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char* uri);
* @{
*/
#define HTTPD_SOCK_ERR_FAIL -1
#define HTTPD_SOCK_ERR_INVALID -2
#define HTTPD_SOCK_ERR_TIMEOUT -3
/**
* @brief Prototype for HTTPDs low-level send function
*
* @note User specified send function must handle errors internally,
* depending upon the set value of errno, and return specific
* HTTPD_SOCK_ERR_ codes, which will eventually be conveyed as
* return value of httpd_send() function
*
* @return
* - Bytes : The number of bytes sent successfully
* - -VE : In case of error
* - HTTPD_SOCK_ERR_INVALID : Invalid arguments
* - HTTPD_SOCK_ERR_TIMEOUT : Timeout/interrupted while calling socket send()
* - HTTPD_SOCK_ERR_FAIL : Unrecoverable error while calling socket send()
*/
typedef int (*httpd_send_func_t)(int sockfd, const char *buf, size_t buf_len, int flags);
/**
* @brief Prototype for HTTPDs low-level recv function
*
* @note User specified recv function must handle errors internally,
* depending upon the set value of errno, and return specific
* HTTPD_SOCK_ERR_ codes, which will eventually be conveyed as
* return value of httpd_req_recv() function
*
* @return
* - Bytes : The number of bytes received successfully
* - -VE : In case of error
* - 0 : Buffer length parameter is zero / connection closed by peer
* - HTTPD_SOCK_ERR_INVALID : Invalid arguments
* - HTTPD_SOCK_ERR_TIMEOUT : Timeout/interrupted while calling socket recv()
* - HTTPD_SOCK_ERR_FAIL : Unrecoverable error while calling socket recv()
*/
typedef int (*httpd_recv_func_t)(int sockfd, char *buf, size_t buf_len, int flags);
@ -460,9 +481,11 @@ int httpd_req_to_sockfd(httpd_req_t *r);
* @param[in] buf_len Length of the buffer
*
* @return
* - Bytes : Number of bytes read into the buffer successfully
* - Zero : When no more data is left for read
* - -1 : On raw recv error / Null arguments / Request pointer is invalid
* - Bytes : Number of bytes read into the buffer successfully
* - 0 : Buffer length parameter is zero / connection closed by peer
* - HTTPD_SOCK_ERR_INVALID : Invalid arguments
* - HTTPD_SOCK_ERR_TIMEOUT : Timeout/interrupted while calling socket recv()
* - HTTPD_SOCK_ERR_FAIL : Unrecoverable error while calling socket recv()
*/
int httpd_req_recv(httpd_req_t *r, char *buf, size_t buf_len);
@ -664,6 +687,7 @@ esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, size_t buf_len)
#define HTTPD_207 "207 Multi-Status" /*!< HTTP Response 207 */
#define HTTPD_400 "400 Bad Request" /*!< HTTP Response 400 */
#define HTTPD_404 "404 Not Found" /*!< HTTP Response 404 */
#define HTTPD_408 "408 Request Timeout" /*!< HTTP Response 408 */
#define HTTPD_500 "500 Internal Server Error" /*!< HTTP Response 500 */
/**
@ -768,6 +792,52 @@ esp_err_t httpd_resp_set_hdr(httpd_req_t *r, const char *field, const char *valu
*/
esp_err_t httpd_resp_send_404(httpd_req_t *r);
/**
* @brief Helper function for HTTP 408
*
* Send HTTP 408 message. If you wish to send additional data in the body of the
* response, please use the lower-level functions directly.
*
* @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.
*
* @param[in] r The request being responded to
*
* @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_408(httpd_req_t *r);
/**
* @brief Helper function for HTTP 500
*
* Send HTTP 500 message. If you wish to send additional data in the body of the
* response, please use the lower-level functions directly.
*
* @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.
*
* @param[in] r The request being responded to
*
* @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_500(httpd_req_t *r);
/**
* @brief Raw HTTP send
*
@ -796,7 +866,9 @@ esp_err_t httpd_resp_send_404(httpd_req_t *r);
*
* @return
* - Bytes : Number of bytes that were sent successfully
* - -1 : Error in raw send / Invalid request / Null arguments
* - HTTPD_SOCK_ERR_INVALID : Invalid arguments
* - HTTPD_SOCK_ERR_TIMEOUT : Timeout/interrupted while calling socket send()
* - HTTPD_SOCK_ERR_FAIL : Unrecoverable error while calling socket send()
*/
int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len);

View file

@ -86,7 +86,7 @@ static esp_err_t verify_url (http_parser *parser)
}
/* Keep URI with terminating null character. Note URI string pointed
* by 'at' is not NULL terminated, therfore use length provided by
* by 'at' is not NULL terminated, therefore use length provided by
* parser while copying the URI to buffer */
strlcpy((char *)r->uri, at, (length + 1));
ESP_LOGD(TAG, LOG_FMT("received URI = %s"), r->uri);
@ -291,7 +291,7 @@ static esp_err_t cb_headers_complete(http_parser *parser)
return ESP_FAIL;
}
/* In absence of body/chunked enoding, http_parser sets content_len to -1 */
/* In absence of body/chunked encoding, http_parser sets content_len to -1 */
r->content_len = ((int)parser->content_length != -1 ?
parser->content_length : 0);
@ -391,14 +391,14 @@ static int read_block(httpd_req_t *req, size_t offset, size_t length)
}
/* Receive data into buffer. If data is pending (from unrecv) then return
* immediatly after receiving pending data, as pending data may just complete
* immediately after receiving pending data, as pending data may just complete
* this request packet. */
int nbytes = httpd_recv_with_opt(req, raux->scratch + offset, buf_len, true);
if (nbytes < 0) {
ESP_LOGD(TAG, LOG_FMT("error in httpd_recv"));
/* Connection error. Notify Timeout in all cases.
* Need some way to check errno for ETIMEDOUT. */
httpd_resp_send_err(req, HTTPD_408_REQ_TIMEOUT);
if (nbytes == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_err(req, HTTPD_408_REQ_TIMEOUT);
}
return -1;
} else if (nbytes == 0) {
ESP_LOGD(TAG, LOG_FMT("connection closed"));
@ -500,7 +500,7 @@ static esp_err_t httpd_parse_req(struct httpd_data *hd)
http_parser parser;
parser_data_t parser_data;
/* Initilaize parser */
/* Initialize parser */
parse_init(r, &parser, &parser_data);
/* Set offset to start of scratch buffer */

View file

@ -55,18 +55,18 @@ esp_err_t httpd_set_recv_override(httpd_req_t *r, httpd_recv_func_t recv_func)
int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len)
{
if (r == NULL || buf == NULL) {
return -1;
return HTTPD_SOCK_ERR_INVALID;
}
if (!httpd_valid_req(r)) {
return -1;
return HTTPD_SOCK_ERR_INVALID;
}
struct httpd_req_aux *ra = r->aux;
int ret = ra->sd->send_fn(ra->sd->fd, buf, buf_len, 0);
if (ret < 0) {
ESP_LOGD(TAG, LOG_FMT("error in send_fn"));
return -1;
return ret;
}
return ret;
}
@ -128,7 +128,16 @@ int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t buf_len, bool halt_aft
int ret = ra->sd->recv_fn(ra->sd->fd, buf, buf_len, 0);
if (ret < 0) {
ESP_LOGD(TAG, LOG_FMT("error in recv_fn"));
return -1;
if ((ret == HTTPD_SOCK_ERR_TIMEOUT) && (pending_len != 0)) {
/* If recv() timeout occurred, but pending data is
* present, return length of pending data.
* This behavior is similar to that of socket recv()
* function, which, in case has only partially read the
* requested length, due to timeout, returns with read
* length, rather than error */
return pending_len;
}
return ret;
}
ESP_LOGD(TAG, LOG_FMT("received length = %d"), ret + pending_len);
@ -367,6 +376,16 @@ esp_err_t httpd_resp_send_404(httpd_req_t *r)
return httpd_resp_send_err(r, HTTPD_404_NOT_FOUND);
}
esp_err_t httpd_resp_send_408(httpd_req_t *r)
{
return httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT);
}
esp_err_t httpd_resp_send_500(httpd_req_t *r)
{
return httpd_resp_send_err(r, HTTPD_500_SERVER_ERROR);
}
esp_err_t httpd_resp_send_err(httpd_req_t *req, httpd_err_resp_t error)
{
const char *msg;
@ -429,12 +448,12 @@ esp_err_t httpd_resp_send_err(httpd_req_t *req, httpd_err_resp_t error)
int httpd_req_recv(httpd_req_t *r, char *buf, size_t buf_len)
{
if (r == NULL || buf == NULL) {
return -1;
return HTTPD_SOCK_ERR_INVALID;
}
if (!httpd_valid_req(r)) {
ESP_LOGW(TAG, LOG_FMT("invalid request"));
return -1;
return HTTPD_SOCK_ERR_INVALID;
}
struct httpd_req_aux *ra = r->aux;
@ -450,8 +469,7 @@ int httpd_req_recv(httpd_req_t *r, char *buf, size_t buf_len)
int ret = httpd_recv(r, buf, buf_len);
if (ret < 0) {
ESP_LOGD(TAG, LOG_FMT("error in httpd_recv"));
ra->remaining_len = 0;
return -1;
return ret;
}
ra->remaining_len -= ret;
ESP_LOGD(TAG, LOG_FMT("received length = %d"), ret);
@ -473,15 +491,44 @@ int httpd_req_to_sockfd(httpd_req_t *r)
return ra->sd->fd;
}
static int httpd_sock_err(const char *ctx, int sockfd)
{
int errval;
int sock_err;
size_t sock_err_len = sizeof(sock_err);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sock_err, &sock_err_len) < 0) {
ESP_LOGE(TAG, LOG_FMT("error calling getsockopt : %d"), errno);
return HTTPD_SOCK_ERR_FAIL;
}
ESP_LOGW(TAG, LOG_FMT("error in %s : %d"), ctx, sock_err);
switch(sock_err) {
case EAGAIN:
case EINTR:
errval = HTTPD_SOCK_ERR_TIMEOUT;
break;
case EINVAL:
case EBADF:
case EFAULT:
case ENOTSOCK:
errval = HTTPD_SOCK_ERR_INVALID;
break;
default:
errval = HTTPD_SOCK_ERR_FAIL;
}
return errval;
}
int httpd_default_send(int sockfd, const char *buf, size_t buf_len, int flags)
{
if (buf == NULL) {
return ESP_ERR_INVALID_ARG;
return HTTPD_SOCK_ERR_INVALID;
}
int ret = send(sockfd, buf, buf_len, flags);
if (ret < 0) {
ESP_LOGW(TAG, LOG_FMT("error in send = %d"), errno);
return httpd_sock_err("send", sockfd);
}
return ret;
}
@ -489,12 +536,12 @@ int httpd_default_send(int sockfd, const char *buf, size_t buf_len, int flags)
int httpd_default_recv(int sockfd, char *buf, size_t buf_len, int flags)
{
if (buf == NULL) {
return ESP_ERR_INVALID_ARG;
return HTTPD_SOCK_ERR_INVALID;
}
int ret = recv(sockfd, buf, buf_len, flags);
if (ret < 0) {
ESP_LOGW(TAG, LOG_FMT("error in recv = %d"), errno);
return httpd_sock_err("recv", sockfd);
}
return ret;
}

View file

@ -216,7 +216,6 @@ esp_err_t httpd_uri(struct httpd_data *hd)
if (uri->handler(req) != ESP_OK) {
/* Handler returns error, this socket should be closed */
ESP_LOGW(TAG, LOG_FMT("uri handler execution failed"));
httpd_resp_send_err(req, HTTPD_408_REQ_TIMEOUT);
return ESP_FAIL;
}
return ESP_OK;

View file

@ -29,15 +29,26 @@ Application Example
/* Our URI handler function to be called during POST /uri request */
esp_err_t post_handler(httpd_req_t *req)
{
/* Read request content */
/* Destination buffer for content of HTTP POST request.
* httpd_req_recv() accepts char* only, but content could
* as well be any binary data (needs type casting).
* In case of string data, null termination will be absent, and
* content length would give length of string */
char[100] content;
/* Truncate if content length larger than the buffer */
size_t recv_size = MIN(req->content_len, sizeof(content));
int ret = httpd_req_recv(req, content, recv_size);
if (ret < 0) {
/* In case of recv error, returning ESP_FAIL will
if (ret <= 0) { /* 0 return value indicates connection closed */
/* Check if timeout occurred */
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
/* In case of timeout one can choose to retry calling
* httpd_req_recv(), but to keep it simple, here we
* respond with an HTTP 408 (Request Timeout) error */
httpd_resp_send_408(req);
}
/* In case of error, returning ESP_FAIL will
* ensure that the underlying socket is closed */
return ESP_FAIL;
}

View file

@ -50,13 +50,17 @@ esp_err_t echo_post_handler(httpd_req_t *req)
int ret;
if (!buf) {
httpd_resp_send_500(req);
return ESP_FAIL;
}
while (off < req->content_len) {
/* Read data received in the request */
ret = httpd_req_recv(req, buf + off, req->content_len - off);
if (ret < 0) {
if (ret <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
free (buf);
return ESP_FAIL;
}
@ -105,7 +109,10 @@ esp_err_t adder_post_handler(httpd_req_t *req)
/* Read data received in the request */
ret = httpd_req_recv(req, buf, sizeof(buf));
if (ret < 0) {
if (ret <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}
@ -137,7 +144,10 @@ esp_err_t leftover_data_post_handler(httpd_req_t *req)
/* Read data received in the request */
ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (ret < 0) {
if (ret <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}

View file

@ -350,7 +350,7 @@ def get_hello(dut, port):
def put_hello(dut, port):
# PUT /hello returns 405'
Utility.console_log("[test] PUT /hello returns 405' =>", end=' ')
Utility.console_log("[test] PUT /hello returns 405 =>", end=' ')
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
conn.request("PUT", "/hello", "Hello")
resp = conn.getresponse()
@ -363,7 +363,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 404 =>", end=' ')
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
conn.request("POST", "/hello", "Hello")
resp = conn.getresponse()
@ -376,7 +376,7 @@ def post_hello(dut, port):
def post_echo(dut, port):
# POST /echo echoes data'
Utility.console_log("[test] POST /echo echoes data' =>", end=' ')
Utility.console_log("[test] POST /echo echoes data =>", end=' ')
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
conn.request("POST", "/echo", "Hello")
resp = conn.getresponse()
@ -392,7 +392,7 @@ def post_echo(dut, port):
def put_echo(dut, port):
# PUT /echo echoes data'
Utility.console_log("[test] PUT /echo echoes data' =>", end=' ')
Utility.console_log("[test] PUT /echo echoes data =>", end=' ')
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
conn.request("PUT", "/echo", "Hello")
resp = conn.getresponse()
@ -408,7 +408,7 @@ def put_echo(dut, port):
def get_echo(dut, port):
# GET /echo returns 404'
Utility.console_log("[test] GET /echo returns 405' =>", end=' ')
Utility.console_log("[test] GET /echo returns 405 =>", end=' ')
conn = http.client.HTTPConnection(dut, int(port), timeout=15)
conn.request("GET", "/echo")
resp = conn.getresponse()
@ -595,7 +595,7 @@ def packet_size_limit_test(dut, port, test_size):
s.request("POST", url=path, body=random_data)
resp = s.getresponse()
if not test_val("Error", "200", str(resp.status)):
if test_val("Error", "408", str(resp.status)):
if test_val("Error", "500", str(resp.status)):
Utility.console_log("Data too large to be allocated")
test_size = test_size//10
else:
@ -619,14 +619,12 @@ def packet_size_limit_test(dut, port, test_size):
def code_500_server_error_test(dut, port):
Utility.console_log("[test] 500 Server Error test =>", end=' ')
s = Session(dut, port)
s.client.sendall(b"abcdefgh\0")
# Sending a very large content length will cause malloc to fail
content_len = 2**31
s.client.sendall(("POST /echo HTTP/1.1\r\nHost: " + dut + "\r\nContent-Length: " + str(content_len) + "\r\n\r\nABCD").encode())
s.read_resp_hdrs()
resp = s.read_resp_data()
# Presently server sends back 400 Bad Request
#if not test_val("Server Error", "500", s.status):
#s.close()
#return False
if not test_val("Server Error", "400", s.status):
if not test_val("Server Error", "500", s.status):
s.close()
return False
s.close()

View file

@ -48,7 +48,10 @@ esp_err_t adder_post_handler(httpd_req_t *req)
/* Read data received in the request */
ret = httpd_req_recv(req, buf, sizeof(buf));
if (ret < 0) {
if (ret <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}
@ -111,7 +114,10 @@ esp_err_t adder_put_handler(httpd_req_t *req)
/* Read data received in the request */
ret = httpd_req_recv(req, buf, sizeof(buf));
if (ret < 0) {
if (ret <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}

View file

@ -122,7 +122,11 @@ esp_err_t echo_post_handler(httpd_req_t *req)
while (remaining > 0) {
/* Read the data for the request */
if ((ret = httpd_req_recv(req, buf,
MIN(remaining, sizeof(buf)))) < 0) {
MIN(remaining, sizeof(buf)))) <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
/* Retry receiving if timeout occurred */
continue;
}
return ESP_FAIL;
}
@ -156,7 +160,10 @@ esp_err_t ctrl_put_handler(httpd_req_t *req)
char buf;
int ret;
if ((ret = httpd_req_recv(req, &buf, 1)) < 0) {
if ((ret = httpd_req_recv(req, &buf, 1)) <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}