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;
}
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);
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()
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()):
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()
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
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()
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 */
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);
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 */
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 */
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;
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);
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 */
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
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 */
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);
.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
*/
}
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 */
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):
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
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")
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):
# 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):
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):
# Close HTTP connection
sess.close()
- return (resp_data == "Hello World!")
+ return "Hello World!" == resp_data
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!")