]> granicus.if.org Git - esp-idf/commitdiff
http_server examples : Updated tests and examples to demonstrate usage of `httpd_regi...
authorAnurag Kar <anurag.kar@espressif.com>
Fri, 1 Feb 2019 13:11:46 +0000 (18:41 +0530)
committerbot <bot@espressif.com>
Mon, 25 Feb 2019 09:13:39 +0000 (09:13 +0000)
examples/protocols/http_server/advanced_tests/main/tests.c
examples/protocols/http_server/advanced_tests/scripts/test.py
examples/protocols/http_server/file_serving/main/file_server.c
examples/protocols/http_server/simple/main/main.c
examples/protocols/http_server/simple/scripts/client.py

index 8845585bee91fb46516e27f172ee75e63c347434..589bf96469ff0a40f355ecd9762852d856c991cd 100644 (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);
index 14371abd808f6f9f5d4e6a6695078ffb2e8157da..ceddc19341fe10a54b4b6a739b9eecba39c001d5 100644 (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()
index 2ca428f9dfac70950ab8749dee6e7377daab3a62..9d301b45bd65107ef8363b5b5d06836e10bad25e 100644 (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);
index d4b7b3869ed82d0c9e093a3ef3c92e16201ede5c..9ada49cabca3a0c2717f87377c06dff8d00d4a37 100644 (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 */
index b43af080140d3e7dc0f40df5c1f9aadafcf0d831..5e070b98b8a5a3d83079efc235ba204af86c2f7e 100644 (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!")