diff --git a/components/esp_http_server/CMakeLists.txt b/components/esp_http_server/CMakeLists.txt
new file mode 100644
index 00000000..700a0b9c
--- /dev/null
+++ b/components/esp_http_server/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(COMPONENT_ADD_INCLUDEDIRS include)
+set(COMPONENT_PRIV_INCLUDEDIRS src/port/esp32 src/util)
+set(COMPONENT_SRCS "src/httpd_main.c"
+                   "src/httpd_parse.c"
+                   "src/httpd_sess.c"
+                   "src/httpd_txrx.c"
+                   "src/httpd_uri.c"
+                   "src/util/ctrl_sock.c")
+
+set(COMPONENT_REQUIRES nghttp)  # for http_parser.h
+set(COMPONENT_PRIV_REQUIRES lwip)
+
+register_component()
diff --git a/components/esp_http_server/Kconfig b/components/esp_http_server/Kconfig
new file mode 100644
index 00000000..323961ad
--- /dev/null
+++ b/components/esp_http_server/Kconfig
@@ -0,0 +1,15 @@
+menu "HTTP Server"
+
+config HTTPD_MAX_REQ_HDR_LEN
+    int "Max HTTP Request Header Length"
+    default 512
+    help
+        This sets the maximum supported size of headers section in HTTP request packet to be processed by the server
+
+config HTTPD_MAX_URI_LEN
+    int "Max HTTP URI Length"
+    default 512
+    help
+        This sets the maximum supported size of HTTP request URI to be processed by the server
+
+endmenu
diff --git a/components/esp_http_server/component.mk b/components/esp_http_server/component.mk
new file mode 100644
index 00000000..e34ca073
--- /dev/null
+++ b/components/esp_http_server/component.mk
@@ -0,0 +1,4 @@
+
+COMPONENT_SRCDIRS := src src/util
+COMPONENT_ADD_INCLUDEDIRS := include
+COMPONENT_PRIV_INCLUDEDIRS := src/port/esp32 src/util
diff --git a/components/esp_http_server/include/esp_http_server.h b/components/esp_http_server/include/esp_http_server.h
new file mode 100644
index 00000000..793f3dd8
--- /dev/null
+++ b/components/esp_http_server/include/esp_http_server.h
@@ -0,0 +1,1188 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _ESP_HTTP_SERVER_H_
+#define _ESP_HTTP_SERVER_H_
+
+#include <stdio.h>
+#include <string.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <http_parser.h>
+#include <sdkconfig.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+note: esp_https_server.h includes a customized copy of this
+initializer that should be kept in sync
+*/
+#define HTTPD_DEFAULT_CONFIG() {                        \
+        .task_priority      = tskIDLE_PRIORITY+5,       \
+        .stack_size         = 4096,                     \
+        .server_port        = 80,                       \
+        .ctrl_port          = 32768,                    \
+        .max_open_sockets   = 7,                        \
+        .max_uri_handlers   = 8,                        \
+        .max_resp_headers   = 8,                        \
+        .backlog_conn       = 5,                        \
+        .lru_purge_enable   = false,                    \
+        .recv_wait_timeout  = 5,                        \
+        .send_wait_timeout  = 5,                        \
+        .global_user_ctx = NULL,                        \
+        .global_user_ctx_free_fn = NULL,                \
+        .global_transport_ctx = NULL,                   \
+        .global_transport_ctx_free_fn = NULL,           \
+        .open_fn = NULL,                                \
+        .close_fn = NULL,                               \
+}
+
+#define ESP_ERR_HTTPD_BASE              (0x8000)                    /*!< Starting number of HTTPD error codes */
+#define ESP_ERR_HTTPD_HANDLERS_FULL     (ESP_ERR_HTTPD_BASE +  1)   /*!< All slots for registering URI handlers have been consumed */
+#define ESP_ERR_HTTPD_HANDLER_EXISTS    (ESP_ERR_HTTPD_BASE +  2)   /*!< URI handler with same method and target URI already registered */
+#define ESP_ERR_HTTPD_INVALID_REQ       (ESP_ERR_HTTPD_BASE +  3)   /*!< Invalid request pointer */
+#define ESP_ERR_HTTPD_RESULT_TRUNC      (ESP_ERR_HTTPD_BASE +  4)   /*!< Result string truncated */
+#define ESP_ERR_HTTPD_RESP_HDR          (ESP_ERR_HTTPD_BASE +  5)   /*!< Response header field larger than supported */
+#define ESP_ERR_HTTPD_RESP_SEND         (ESP_ERR_HTTPD_BASE +  6)   /*!< Error occured while sending response packet */
+#define ESP_ERR_HTTPD_ALLOC_MEM         (ESP_ERR_HTTPD_BASE +  7)   /*!< Failed to dynamically allocate memory for resource */
+#define ESP_ERR_HTTPD_TASK              (ESP_ERR_HTTPD_BASE +  8)   /*!< Failed to launch server task/thread */
+
+/* ************** Group: Initialization ************** */
+/** @name Initialization
+ * APIs related to the Initialization of the web server
+ * @{
+ */
+
+/**
+ * @brief   HTTP Server Instance Handle
+ *
+ * Every instance of the server will have a unique handle.
+ */
+typedef void* httpd_handle_t;
+
+/**
+ * @brief   HTTP Method Type wrapper over "enum http_method"
+ *          available in "http_parser" library
+ */
+typedef enum http_method httpd_method_t;
+
+/**
+ * @brief  Prototype for freeing context data (if any)
+ * @param[in] ctx : object to free
+ */
+typedef void (*httpd_free_ctx_fn_t)(void *ctx);
+
+/**
+ * @brief  Function prototype for opening a session.
+ *
+ * Called immediately after the socket was opened to set up the send/recv functions and
+ * other parameters of the socket.
+ *
+ * @param[in] hd : server instance
+ * @param[in] sockfd : session socket file descriptor
+ * @return status
+ */
+typedef esp_err_t (*httpd_open_func_t)(httpd_handle_t hd, int sockfd);
+
+/**
+ * @brief  Function prototype for closing a session.
+ *
+ * @note   It's possible that the socket descriptor is invalid at this point, the function
+ *         is called for all terminated sessions. Ensure proper handling of return codes.
+ *
+ * @param[in] hd : server instance
+ * @param[in] sockfd : session socket file descriptor
+ */
+typedef void (*httpd_close_func_t)(httpd_handle_t hd, int sockfd);
+
+/**
+ * @brief   HTTP Server Configuration Structure
+ *
+ * @note    Use HTTPD_DEFAULT_CONFIG() to initialize the configuration
+ *          to a default value and then modify only those fields that are
+ *          specifically determined by the use case.
+ */
+typedef struct httpd_config {
+    unsigned    task_priority;      /*!< Priority of FreeRTOS task which runs the server */
+    size_t      stack_size;         /*!< The maximum stack size allowed for the server task */
+
+    /**
+     * TCP Port number for receiving and transmitting HTTP traffic
+     */
+    uint16_t    server_port;
+
+    /**
+     * UDP Port number for asynchronously exchanging control signals
+     * between various components of the server
+     */
+    uint16_t    ctrl_port;
+
+    uint16_t    max_open_sockets;   /*!< Max number of sockets/clients connected at any time*/
+    uint16_t    max_uri_handlers;   /*!< Maximum allowed uri handlers */
+    uint16_t    max_resp_headers;   /*!< Maximum allowed additional headers in HTTP response */
+    uint16_t    backlog_conn;       /*!< Number of backlog connections */
+    bool        lru_purge_enable;   /*!< Purge "Least Recently Used" connection */
+    uint16_t    recv_wait_timeout;  /*!< Timeout for recv function (in seconds)*/
+    uint16_t    send_wait_timeout;  /*!< Timeout for send function (in seconds)*/
+
+    /**
+     * Global user context.
+     *
+     * This field can be used to store arbitrary user data within the server context.
+     * The value can be retrieved using the server handle, available e.g. in the httpd_req_t struct.
+     *
+     * When shutting down, the server frees up the user context by
+     * calling free() on the global_user_ctx field. If you wish to use a custom
+     * function for freeing the global user context, please specify that here.
+     */
+    void * global_user_ctx;
+
+    /**
+     * Free function for global user context
+     */
+    httpd_free_ctx_fn_t global_user_ctx_free_fn;
+
+    /**
+     * Global transport context.
+     *
+     * Similar to global_user_ctx, but used for session encoding or encryption (e.g. to hold the SSL context).
+     * It will be freed using free(), unless global_transport_ctx_free_fn is specified.
+     */
+    void * global_transport_ctx;
+
+    /**
+     * Free function for global transport context
+     */
+    httpd_free_ctx_fn_t global_transport_ctx_free_fn;
+
+    /**
+     * Custom session opening callback.
+     *
+     * Called on a new session socket just after accept(), but before reading any data.
+     *
+     * This is an opportunity to set up e.g. SSL encryption using global_transport_ctx
+     * and the send/recv/pending session overrides.
+     *
+     * If a context needs to be maintained between these functions, store it in the session using
+     * httpd_sess_set_transport_ctx() and retrieve it later with httpd_sess_get_transport_ctx()
+     */
+    httpd_open_func_t open_fn;
+
+    /**
+     * Custom session closing callback.
+     *
+     * Called when a session is deleted, before freeing user and transport contexts and before
+     * closing the socket. This is a place for custom de-init code common to all sockets.
+     *
+     * Set the user or transport context to NULL if it was freed here, so the server does not
+     * try to free it again.
+     *
+     * This function is run for all terminated sessions, including sessions where the socket
+     * was closed by the network stack - that is, the file descriptor may not be valid anymore.
+     */
+    httpd_close_func_t close_fn;
+} httpd_config_t;
+
+/**
+ * @brief Starts the web server
+ *
+ * Create an instance of HTTP server and allocate memory/resources for it
+ * depending upon the specified configuration.
+ *
+ * Example usage:
+ * @code{c}
+ *
+ * //Function for starting the webserver
+ * httpd_handle_t start_webserver(void)
+ * {
+ *      // Generate default configuration
+ *      httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+ *
+ *      // Empty handle to http_server
+ *      httpd_handle_t server = NULL;
+ *
+ *      // Start the httpd server
+ *      if (httpd_start(&server, &config) == ESP_OK) {
+ *          // Register URI handlers
+ *          httpd_register_uri_handler(server, &uri_get);
+ *          httpd_register_uri_handler(server, &uri_post);
+ *      }
+ *      // If server failed to start, handle will be NULL
+ *      return server;
+ * }
+ *
+ * @endcode
+ *
+ * @param[in]  config : Configuration for new instance of the server
+ * @param[out] handle : Handle to newly created instance of the server. NULL on error
+ * @return
+ *  - ESP_OK    : Instance created successfully
+ *  - ESP_ERR_INVALID_ARG      : Null argument(s)
+ *  - ESP_ERR_HTTPD_ALLOC_MEM  : Failed to allocate memory for instance
+ *  - ESP_ERR_HTTPD_TASK       : Failed to launch server task
+ */
+esp_err_t  httpd_start(httpd_handle_t *handle, const httpd_config_t *config);
+
+/**
+ * @brief Stops the web server
+ *
+ * Deallocates memory/resources used by an HTTP server instance and
+ * deletes it. Once deleted the handle can no longer be used for accessing
+ * the instance.
+ *
+ * Example usage:
+ * @code{c}
+ *
+ * // Function for stopping the webserver
+ * void stop_webserver(httpd_handle_t server)
+ * {
+ *      // Ensure handle is non NULL
+ *      if (server != NULL) {
+ *          // Stop the httpd server
+ *          httpd_stop(server);
+ *      }
+ * }
+ *
+ * @endcode
+ *
+ * @param[in] handle Handle to server returned by httpd_start
+ * @return
+ *  - ESP_OK : Server stopped successfully
+ *  - ESP_ERR_INVALID_ARG : Handle argument is Null
+ */
+esp_err_t httpd_stop(httpd_handle_t handle);
+
+/** End of Group Initialization
+ * @}
+ */
+
+/* ************** Group: URI Handlers ************** */
+/** @name URI Handlers
+ * APIs related to the URI handlers
+ * @{
+ */
+
+/* Max supported HTTP request header length */
+#define HTTPD_MAX_REQ_HDR_LEN CONFIG_HTTPD_MAX_REQ_HDR_LEN
+
+/* Max supported HTTP request URI length */
+#define HTTPD_MAX_URI_LEN CONFIG_HTTPD_MAX_URI_LEN
+
+/**
+ * @brief HTTP Request Data Structure
+ */
+typedef struct httpd_req {
+    httpd_handle_t  handle;                     /*!< Handle to server instance */
+    int             method;                     /*!< The type of HTTP request, -1 if unsupported method */
+    const char      uri[HTTPD_MAX_URI_LEN + 1]; /*!< The URI of this request (1 byte extra for null termination) */
+    size_t          content_len;                /*!< Length of the request body */
+    void           *aux;                        /*!< Internally used members */
+
+    /**
+     * User context pointer passed during URI registration.
+     */
+    void *user_ctx;
+
+    /**
+     * Session Context Pointer
+     *
+     * A session context. Contexts are maintained across 'sessions' for a
+     * given open TCP connection. One session could have multiple request
+     * responses. The web server will ensure that the context persists
+     * across all these request and responses.
+     *
+     * By default, this is NULL. URI Handlers can set this to any meaningful
+     * value.
+     *
+     * If the underlying socket gets closed, and this pointer is non-NULL,
+     * the web server will free up the context by calling free(), unless
+     * free_ctx function is set.
+     */
+    void *sess_ctx;
+
+    /**
+     * Pointer to free context hook
+     *
+     * Function to free session context
+     *
+     * If the web server's socket closes, it frees up the session context by
+     * calling free() on the sess_ctx member. If you wish to use a custom
+     * function for freeing the session context, please specify that here.
+     */
+    httpd_free_ctx_fn_t free_ctx;
+} httpd_req_t;
+
+/**
+ * @brief Structure for URI handler
+ */
+typedef struct httpd_uri {
+    const char       *uri;    /*!< The URI to handle */
+    httpd_method_t    method; /*!< Method supported by the URI */
+
+    /**
+     * Handler to call for supported request method. This must
+     * return ESP_OK, or else the underlying socket will be closed.
+     */
+    esp_err_t (*handler)(httpd_req_t *r);
+
+    /**
+     * Pointer to user context data which will be available to handler
+     */
+    void *user_ctx;
+} httpd_uri_t;
+
+/**
+ * @brief   Registers a URI handler
+ *
+ * @note    URI handlers can be registered in real time as long as the
+ *          server handle is valid.
+ *
+ * Example usage:
+ * @code{c}
+ *
+ * esp_err_t my_uri_handler(httpd_req_t* req)
+ * {
+ *     // Recv , Process and Send
+ *     ....
+ *     ....
+ *     ....
+ *
+ *     // Fail condition
+ *     if (....) {
+ *         // Return fail to close session //
+ *         return ESP_FAIL;
+ *     }
+ *
+ *     // On success
+ *     return ESP_OK;
+ * }
+ *
+ * // URI handler structure
+ * httpd_uri_t my_uri {
+ *     .uri      = "/my_uri/path/xyz",
+ *     .method   = HTTPD_GET,
+ *     .handler  = my_uri_handler,
+ *     .user_ctx = NULL
+ * };
+ *
+ * // Register handler
+ * if (httpd_register_uri_handler(server_handle, &my_uri) != ESP_OK) {
+ *    // If failed to register handler
+ *    ....
+ * }
+ *
+ * @endcode
+ *
+ * @param[in] handle      handle to HTTPD server instance
+ * @param[in] uri_handler pointer to handler that needs to be registered
+ *
+ * @return
+ *  - ESP_OK : On successfully registering the handler
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ *  - ESP_ERR_HTTPD_HANDLERS_FULL  : If no slots left for new handler
+ *  - ESP_ERR_HTTPD_HANDLER_EXISTS : If handler with same URI and
+ *                                   method is already registered
+ */
+esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
+                                     const httpd_uri_t *uri_handler);
+
+/**
+ * @brief   Unregister a URI handler
+ *
+ * @param[in] handle    handle to HTTPD server instance
+ * @param[in] uri       URI string
+ * @param[in] method    HTTP method
+ *
+ * @return
+ *  - ESP_OK : On successfully deregistering the handler
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ *  - ESP_ERR_NOT_FOUND   : Handler with specified URI and method not found
+ */
+esp_err_t httpd_unregister_uri_handler(httpd_handle_t handle,
+                                       const char *uri, httpd_method_t method);
+
+/**
+ * @brief   Unregister all URI handlers with the specified uri string
+ *
+ * @param[in] handle   handle to HTTPD server instance
+ * @param[in] uri      uri string specifying all handlers that need
+ *                     to be deregisterd
+ *
+ * @return
+ *  - ESP_OK : On successfully deregistering all such handlers
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ *  - ESP_ERR_NOT_FOUND   : No handler registered with specified uri string
+ */
+esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char* uri);
+
+/** End of URI Handlers
+ * @}
+ */
+
+/* ************** Group: TX/RX ************** */
+/** @name TX / RX
+ * Prototype for HTTPDs low-level send/recv functions
+ * @{
+ */
+
+#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
+ *
+ * @param[in] hd      : server instance
+ * @param[in] sockfd  : session socket file descriptor
+ * @param[in] buf     : buffer with bytes to send
+ * @param[in] buf_len : data size
+ * @param[in] flags   : flags for the send() function
+ * @return
+ *  - Bytes : The number of bytes sent successfully
+ *  - 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)(httpd_handle_t hd, 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
+ *
+ * @param[in] hd      : server instance
+ * @param[in] sockfd  : session socket file descriptor
+ * @param[in] buf     : buffer with bytes to send
+ * @param[in] buf_len : data size
+ * @param[in] flags   : flags for the send() function
+ * @return
+ *  - Bytes : The number of bytes received 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()
+ */
+typedef int (*httpd_recv_func_t)(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags);
+
+/**
+ * @brief  Prototype for HTTPDs low-level "get pending bytes" function
+ *
+ * @note   User specified pending function must handle errors internally,
+ *         depending upon the set value of errno, and return specific
+ *         HTTPD_SOCK_ERR_ codes, which will be handled accordingly in
+ *         the server task.
+ *
+ * @param[in] hd : server instance
+ * @param[in] sockfd : session socket file descriptor
+ * @return
+ *  - Bytes : The number of bytes waiting to be received
+ *  - HTTPD_SOCK_ERR_INVALID  : Invalid arguments
+ *  - HTTPD_SOCK_ERR_TIMEOUT  : Timeout/interrupted while calling socket pending()
+ *  - HTTPD_SOCK_ERR_FAIL     : Unrecoverable error while calling socket pending()
+ */
+typedef int (*httpd_pending_func_t)(httpd_handle_t hd, int sockfd);
+
+/** End of TX / RX
+ * @}
+ */
+
+/* ************** Group: Request/Response ************** */
+/** @name Request / Response
+ * APIs related to the data send/receive by URI handlers.
+ * These APIs are supposed to be called only from the context of
+ * a URI handler where httpd_req_t* request pointer is valid.
+ * @{
+ */
+
+/**
+ * @brief   Override web server's receive function (by session FD)
+ *
+ * This function overrides the web server's receive function. This same function is
+ * used to read HTTP request packets.
+ *
+ * @note    This API is supposed to be called either from the context of
+ *          - an http session APIs where sockfd is a valid parameter
+ *          - a URI handler where sockfd is obtained using httpd_req_to_sockfd()
+ *
+ * @param[in] hd        HTTPD instance handle
+ * @param[in] sockfd    Session socket FD
+ * @param[in] recv_func The receive function to be set for this session
+ *
+ * @return
+ *  - ESP_OK : On successfully registering override
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ */
+esp_err_t httpd_sess_set_recv_override(httpd_handle_t hd, int sockfd, httpd_recv_func_t recv_func);
+
+/**
+ * @brief   Override web server's send function (by session FD)
+ *
+ * This function overrides the web server's send function. This same function is
+ * used to send out any response to any HTTP request.
+ *
+ * @note    This API is supposed to be called either from the context of
+ *          - an http session APIs where sockfd is a valid parameter
+ *          - a URI handler where sockfd is obtained using httpd_req_to_sockfd()
+ *
+ * @param[in] hd        HTTPD instance handle
+ * @param[in] sockfd    Session socket FD
+ * @param[in] send_func The send function to be set for this session
+ *
+ * @return
+ *  - ESP_OK : On successfully registering override
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ */
+esp_err_t httpd_sess_set_send_override(httpd_handle_t hd, int sockfd, httpd_send_func_t send_func);
+
+/**
+ * @brief   Override web server's pending function (by session FD)
+ *
+ * This function overrides the web server's pending function. This function is
+ * used to test for pending bytes in a socket.
+ *
+ * @note    This API is supposed to be called either from the context of
+ *          - an http session APIs where sockfd is a valid parameter
+ *          - a URI handler where sockfd is obtained using httpd_req_to_sockfd()
+ *
+ * @param[in] hd           HTTPD instance handle
+ * @param[in] sockfd       Session socket FD
+ * @param[in] pending_func The receive function to be set for this session
+ *
+ * @return
+ *  - ESP_OK : On successfully registering override
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ */
+esp_err_t httpd_sess_set_pending_override(httpd_handle_t hd, int sockfd, httpd_pending_func_t pending_func);
+
+/**
+ * @brief   Get the Socket Descriptor from the HTTP request
+ *
+ * This API will return the socket descriptor of the session for
+ * which URI handler was executed on reception of HTTP request.
+ * This is useful when user wants to call functions that require
+ * session socket fd, from within a URI handler, ie. :
+ *      httpd_sess_get_ctx(),
+ *      httpd_sess_trigger_close(),
+ *      httpd_sess_update_timestamp().
+ *
+ * @note    This API is supposed to be called only from the context of
+ *          a URI handler where httpd_req_t* request pointer is valid.
+ *
+ * @param[in] r The request whose socket descriptor should be found
+ *
+ * @return
+ *  - Socket descriptor : The socket descriptor for this request
+ *  - -1 : Invalid/NULL request pointer
+ */
+int httpd_req_to_sockfd(httpd_req_t *r);
+
+/**
+ * @brief   API to read content data from the HTTP request
+ *
+ * This API will read HTTP content data from the HTTP request into
+ * provided buffer. Use content_len provided in httpd_req_t structure
+ * to know the length of data to be fetched. If content_len is too
+ * large for the buffer then user may have to make multiple calls to
+ * this function, each time fetching 'buf_len' number of bytes,
+ * while the pointer to content data is incremented internally by
+ * the same number.
+ *
+ * @note
+ *  - This API is supposed to be called only from the context of
+ *    a URI handler where httpd_req_t* request pointer is valid.
+ *  - If an error is returned, the URI handler must further return an error.
+ *    This will ensure that the erroneous socket is closed and cleaned up by
+ *    the web server.
+ *  - Presently Chunked Encoding is not supported
+ *
+ * @param[in] r         The request being responded to
+ * @param[in] buf       Pointer to a buffer that the data will be read into
+ * @param[in] buf_len   Length of the buffer
+ *
+ * @return
+ *  - 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);
+
+/**
+ * @brief   Search for a field in request headers and
+ *          return the string length of it's value
+ *
+ * @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 httpd_resp_send() 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
+ * @param[in]  field    The header field to be searched in the request
+ *
+ * @return
+ *  - Length    : If field is found in the request URL
+ *  - Zero      : Field not found / Invalid request / Null arguments
+ */
+size_t httpd_req_get_hdr_value_len(httpd_req_t *r, const char *field);
+
+/**
+ * @brief   Get the value string of a field from the request headers
+ *
+ * @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 httpd_resp_send() API is called all request headers
+ *    are purged, so request headers need be copied into separate
+ *    buffers if they are required later.
+ *  - If output size is greater than input, then the value is truncated,
+ *    accompanied by truncation error as return value.
+ *  - Use httpd_req_get_hdr_value_len() to know the right buffer length
+ *
+ * @param[in]  r        The request being responded to
+ * @param[in]  field    The field to be searched in the header
+ * @param[out] val      Pointer to the buffer into which the value will be copied if the field is found
+ * @param[in]  val_size Size of the user buffer "val"
+ *
+ * @return
+ *  - ESP_OK : Field found in the request header and value string copied
+ *  - ESP_ERR_NOT_FOUND          : Key not found
+ *  - ESP_ERR_INVALID_ARG        : Null arguments
+ *  - ESP_ERR_HTTPD_INVALID_REQ  : Invalid HTTP request pointer
+ *  - ESP_ERR_HTTPD_RESULT_TRUNC : Value string truncated
+ */
+esp_err_t httpd_req_get_hdr_value_str(httpd_req_t *r, const char *field, char *val, size_t val_size);
+
+/**
+ * @brief   Get Query string length from the request URL
+ *
+ * @note    This API is supposed to be called only from the context of
+ *          a URI handler where httpd_req_t* request pointer is valid
+ *
+ * @param[in]  r    The request being responded to
+ *
+ * @return
+ *  - Length    : Query is found in the request URL
+ *  - Zero      : Query not found / Null arguments / Invalid request
+ */
+size_t httpd_req_get_url_query_len(httpd_req_t *r);
+
+/**
+ * @brief   Get Query string from the request URL
+ *
+ * @note
+ *  - Presently, the user can fetch the full URL query string, but decoding
+ *    will have to be performed by the user. Request headers can be read using
+ *    httpd_req_get_hdr_value_str() to know the 'Content-Type' (eg. Content-Type:
+ *    application/x-www-form-urlencoded) and then the appropriate decoding
+ *    algorithm needs to be applied.
+ *  - This API is supposed to be called only from the context of
+ *    a URI handler where httpd_req_t* request pointer is valid
+ *  - If output size is greater than input, then the value is truncated,
+ *    accompanied by truncation error as return value
+ *  - Use httpd_req_get_url_query_len() to know the right buffer length
+ *
+ * @param[in]  r         The request being responded to
+ * @param[out] buf       Pointer to the buffer into which the query string will be copied (if found)
+ * @param[in]  buf_len   Length of output buffer
+ *
+ * @return
+ *  - ESP_OK : Query is found in the request URL and copied to buffer
+ *  - ESP_ERR_NOT_FOUND          : Query not found
+ *  - ESP_ERR_INVALID_ARG        : Null arguments
+ *  - ESP_ERR_HTTPD_INVALID_REQ  : Invalid HTTP request pointer
+ *  - ESP_ERR_HTTPD_RESULT_TRUNC : Query string truncated
+ */
+esp_err_t httpd_req_get_url_query_str(httpd_req_t *r, char *buf, size_t buf_len);
+
+/**
+ * @brief   Helper function to get a URL query tag from a query
+ *          string of the type param1=val1&param2=val2
+ *
+ * @note
+ *  - The components of URL query string (keys and values) are not URLdecoded.
+ *    The user must check for 'Content-Type' field in the request headers and
+ *    then depending upon the specified encoding (URLencoded or otherwise) apply
+ *    the appropriate decoding algorithm.
+ *  - If actual value size is greater than val_size, then the value is truncated,
+ *    accompanied by truncation error as return value.
+ *
+ * @param[in]  qry       Pointer to query string
+ * @param[in]  key       The key to be searched in the query string
+ * @param[out] val       Pointer to the buffer into which the value will be copied if the key is found
+ * @param[in]  val_size  Size of the user buffer "val"
+ *
+ * @return
+ *  - ESP_OK : Key is found in the URL query string and copied to buffer
+ *  - ESP_ERR_NOT_FOUND          : Key not found
+ *  - ESP_ERR_INVALID_ARG        : Null arguments
+ *  - ESP_ERR_HTTPD_RESULT_TRUNC : Value string truncated
+ */
+esp_err_t httpd_query_key_value(const char *qry, const char *key, char *val, size_t val_size);
+
+/**
+ * @brief   API to send a complete HTTP response.
+ *
+ * This API will send the data as an HTTP response to the request.
+ * This assumes that you have the entire response ready in a single
+ * buffer. If you wish to send response in incremental chunks use
+ * httpd_resp_send_chunk() instead.
+ *
+ * If no status code and content-type were set, by default this
+ * will send 200 OK status code and content type as text/html.
+ * You may call the following functions before this API to configure
+ * the response headers :
+ *      httpd_resp_set_status() - for setting the HTTP status string,
+ *      httpd_resp_set_type()   - for setting the Content Type,
+ *      httpd_resp_set_hdr()    - for appending any additional field
+ *                                value entries in the response header
+ *
+ * @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, the request has been responded to.
+ *  - No additional data can then be sent for the request.
+ *  - 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
+ * @param[in] buf       Buffer from where the content is to be fetched
+ * @param[in] buf_len   Length of the buffer, -1 to use strlen()
+ *
+ * @return
+ *  - ESP_OK : On successfully sending the response packet
+ *  - ESP_ERR_INVALID_ARG : Null request pointer
+ *  - ESP_ERR_HTTPD_RESP_HDR    : Essential headers are too large for internal buffer
+ *  - ESP_ERR_HTTPD_RESP_SEND   : Error in raw send
+ *  - ESP_ERR_HTTPD_INVALID_REQ : Invalid request
+ */
+esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len);
+
+/**
+ * @brief   API to send one HTTP chunk
+ *
+ * This API will send the data as an HTTP response to the
+ * request. This API will use chunked-encoding and send the response
+ * in the form of chunks. If you have the entire response contained in
+ * a single buffer, please use httpd_resp_send() instead.
+ *
+ * If no status code and content-type were set, by default this will
+ * send 200 OK status code and content type as text/html. You may
+ * call the following functions before this API to configure the
+ * response headers
+ *      httpd_resp_set_status() - for setting the HTTP status string,
+ *      httpd_resp_set_type()   - for setting the Content Type,
+ *      httpd_resp_set_hdr()    - for appending any additional field
+ *                                value entries in the response header
+ *
+ * @note
+ * - This API is supposed to be called only from the context of
+ *   a URI handler where httpd_req_t* request pointer is valid.
+ * - When you are finished sending all your chunks, you must call
+ *   this function with buf_len as 0.
+ * - 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
+ * @param[in] buf       Pointer to a buffer that stores the data
+ * @param[in] buf_len   Length of the data from the buffer that should be sent out, -1 to use strlen()
+ *
+ * @return
+ *  - ESP_OK : On successfully sending the response packet chunk
+ *  - ESP_ERR_INVALID_ARG : Null request pointer
+ *  - ESP_ERR_HTTPD_RESP_HDR    : Essential headers are too large for internal buffer
+ *  - ESP_ERR_HTTPD_RESP_SEND   : Error in raw send
+ *  - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer
+ */
+esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len);
+
+/* Some commonly used status codes */
+#define HTTPD_200      "200 OK"                     /*!< HTTP Response 200 */
+#define HTTPD_204      "204 No Content"             /*!< HTTP Response 204 */
+#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 */
+
+/**
+ * @brief   API to set the HTTP status code
+ *
+ * This API sets the status of the HTTP response to the value specified.
+ * By default, the '200 OK' response is sent as the response.
+ *
+ * @note
+ *  - This API is supposed to be called only from the context of
+ *    a URI handler where httpd_req_t* request pointer is valid.
+ *  - This API only sets the status to this value. The status isn't
+ *    sent out until any of the send APIs is executed.
+ *  - Make sure that the lifetime of the status string is valid till
+ *    send function is called.
+ *
+ * @param[in] r         The request being responded to
+ * @param[in] status    The HTTP status code of this response
+ *
+ * @return
+ *  - ESP_OK : On success
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ *  - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer
+ */
+esp_err_t httpd_resp_set_status(httpd_req_t *r, const char *status);
+
+/* Some commonly used content types */
+#define HTTPD_TYPE_JSON   "application/json"            /*!< HTTP Content type JSON */
+#define HTTPD_TYPE_TEXT   "text/html"                   /*!< HTTP Content type text/HTML */
+#define HTTPD_TYPE_OCTET  "application/octet-stream"    /*!< HTTP Content type octext-stream */
+
+/**
+ * @brief   API to set the HTTP content type
+ *
+ * This API sets the 'Content Type' field of the response.
+ * The default content type is 'text/html'.
+ *
+ * @note
+ *  - This API is supposed to be called only from the context of
+ *    a URI handler where httpd_req_t* request pointer is valid.
+ *  - This API only sets the content type to this value. The type
+ *    isn't sent out until any of the send APIs is executed.
+ *  - Make sure that the lifetime of the type string is valid till
+ *    send function is called.
+ *
+ * @param[in] r     The request being responded to
+ * @param[in] type  The Content Type of the response
+ *
+ * @return
+ *  - ESP_OK   : On success
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ *  - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer
+ */
+esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type);
+
+/**
+ * @brief   API to append any additional headers
+ *
+ * This API sets any additional header fields that need to be sent in the response.
+ *
+ * @note
+ *  - This API is supposed to be called only from the context of
+ *    a URI handler where httpd_req_t* request pointer is valid.
+ *  - The header isn't sent out until any of the send APIs is executed.
+ *  - The maximum allowed number of additional headers is limited to
+ *    value of max_resp_headers in config structure.
+ *  - Make sure that the lifetime of the field value strings are valid till
+ *    send function is called.
+ *
+ * @param[in] r     The request being responded to
+ * @param[in] field The field name of the HTTP header
+ * @param[in] value The value of this HTTP header
+ *
+ * @return
+ *  - ESP_OK : On successfully appending new header
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ *  - ESP_ERR_HTTPD_RESP_HDR    : Total additional headers exceed max allowed
+ *  - ESP_ERR_HTTPD_INVALID_REQ : Invalid request pointer
+ */
+esp_err_t httpd_resp_set_hdr(httpd_req_t *r, const char *field, const char *value);
+
+/**
+ * @brief   Helper function for HTTP 404
+ *
+ * Send HTTP 404 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_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
+ *
+ * Call this API if you wish to construct your custom response packet.
+ * When using this, all essential header, eg. HTTP version, Status Code,
+ * Content Type and Length, Encoding, etc. will have to be constructed
+ * manually, and HTTP delimeters (CRLF) will need to be placed correctly
+ * for separating sub-sections of the HTTP response packet.
+ *
+ * If the send override function is set, this API will end up
+ * calling that function eventually to send data out.
+ *
+ * @note
+ *  - This API is supposed to be called only from the context of
+ *    a URI handler where httpd_req_t* request pointer is valid.
+ *  - Unless the response has the correct HTTP structure (which the
+ *    user must now ensure) it is not guaranteed that it will be
+ *    recognized by the client. For most cases, you wouldn't have
+ *    to call this API, but you would rather use either of :
+ *          httpd_resp_send(),
+ *          httpd_resp_send_chunk()
+ *
+ * @param[in] r         The request being responded to
+ * @param[in] buf       Buffer from where the fully constructed packet is to be read
+ * @param[in] buf_len   Length of the buffer
+ *
+ * @return
+ *  - Bytes : Number of bytes that were sent successfully
+ *  - 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);
+
+/** End of Request / Response
+ * @}
+ */
+
+/* ************** Group: Session ************** */
+/** @name Session
+ * Functions for controlling sessions and accessing context data
+ * @{
+ */
+
+/**
+ * @brief   Get session context from socket descriptor
+ *
+ * Typically if a session context is created, it is available to URI handlers
+ * through the httpd_req_t structure. But, there are cases where the web
+ * server's send/receive functions may require the context (for example, for
+ * accessing keying information etc). Since the send/receive function only have
+ * the socket descriptor at their disposal, this API provides them with a way to
+ * retrieve the session context.
+ *
+ * @param[in] handle    Handle to server returned by httpd_start
+ * @param[in] sockfd    The socket descriptor for which the context should be extracted.
+ *
+ * @return
+ *  - void* : Pointer to the context associated with this session
+ *  - NULL  : Empty context / Invalid handle / Invalid socket fd
+ */
+void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd);
+
+/**
+ * @brief   Set session context by socket descriptor
+ *
+ * @param[in] handle    Handle to server returned by httpd_start
+ * @param[in] sockfd    The socket descriptor for which the context should be extracted.
+ * @param[in] ctx       Context object to assign to the session
+ * @param[in] free_fn   Function that should be called to free the context
+ */
+void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn);
+
+/**
+ * @brief   Get session 'transport' context by socket descriptor
+ * @see     httpd_sess_get_ctx()
+ *
+ * This context is used by the send/receive functions, for example to manage SSL context.
+ *
+ * @param[in] handle    Handle to server returned by httpd_start
+ * @param[in] sockfd    The socket descriptor for which the context should be extracted.
+ * @return
+ *  - void* : Pointer to the transport context associated with this session
+ *  - NULL  : Empty context / Invalid handle / Invalid socket fd
+ */
+void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd);
+
+/**
+ * @brief   Set session 'transport' context by socket descriptor
+ * @see     httpd_sess_set_ctx()
+ *
+ * @param[in] handle    Handle to server returned by httpd_start
+ * @param[in] sockfd    The socket descriptor for which the context should be extracted.
+ * @param[in] ctx       Transport context object to assign to the session
+ * @param[in] free_fn   Function that should be called to free the transport context
+ */
+void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn);
+
+/**
+ * @brief   Get HTTPD global user context (it was set in the server config struct)
+ *
+ * @param[in] handle    Handle to server returned by httpd_start
+ * @return global user context
+ */
+void *httpd_get_global_user_ctx(httpd_handle_t handle);
+
+/**
+ * @brief   Get HTTPD global transport context (it was set in the server config struct)
+ *
+ * @param[in] handle    Handle to server returned by httpd_start
+ * @return global transport context
+ */
+void *httpd_get_global_transport_ctx(httpd_handle_t handle);
+
+/**
+ * @brief   Trigger an httpd session close externally
+ *
+ * @note    Calling this API is only required in special circumstances wherein
+ *          some application requires to close an httpd client session asynchronously.
+ *
+ * @param[in] handle    Handle to server returned by httpd_start
+ * @param[in] sockfd    The socket descriptor of the session to be closed
+ *
+ * @return
+ *  - ESP_OK    : On successfully initiating closure
+ *  - ESP_FAIL  : Failure to queue work
+ *  - ESP_ERR_NOT_FOUND   : Socket fd not found
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ */
+esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd);
+
+/**
+ * @brief   Update timestamp for a given socket
+ *
+ * Timestamps are internally associated with each session to monitor
+ * how recently a session exchanged traffic. When LRU purge is enabled,
+ * if a client is requesting for connection but maximum number of
+ * sockets/sessions is reached, then the session having the earliest
+ * timestamp is closed automatically.
+ *
+ * Updating the timestamp manually prevents the socket from being purged
+ * due to the Least Recently Used (LRU) logic, even though it might not
+ * have received traffic for some time. This is useful when all open
+ * sockets/session are frequently exchanging traffic but the user specifically
+ * wants one of the sessions to be kept open, irrespective of when it last
+ * exchanged a packet.
+ *
+ * @note    Calling this API is only necessary if the LRU Purge Enable option
+ *          is enabled.
+ *
+ * @param[in] handle    Handle to server returned by httpd_start
+ * @param[in] sockfd    The socket descriptor of the session for which timestamp
+ *                      is to be updated
+ *
+ * @return
+ *  - ESP_OK : Socket found and timestamp updated
+ *  - ESP_ERR_NOT_FOUND   : Socket not found
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ */
+esp_err_t httpd_sess_update_timestamp(httpd_handle_t handle, int sockfd);
+
+/** End of Session
+ * @}
+ */
+
+/* ************** Group: Work Queue ************** */
+/** @name Work Queue
+ * APIs related to the HTTPD Work Queue
+ * @{
+ */
+
+/**
+ * @brief   Prototype of the HTTPD work function
+ *          Please refer to httpd_queue_work() for more details.
+ * @param[in] arg   The arguments for this work function
+ */
+typedef void (*httpd_work_fn_t)(void *arg);
+
+/**
+ * @brief   Queue execution of a function in HTTPD's context
+ *
+ * This API queues a work function for asynchronous execution
+ *
+ * @note    Some protocols require that the web server generate some asynchronous data
+ *          and send it to the persistently opened connection. This facility is for use
+ *          by such protocols.
+ *
+ * @param[in] handle    Handle to server returned by httpd_start
+ * @param[in] work      Pointer to the function to be executed in the HTTPD's context
+ * @param[in] arg       Pointer to the arguments that should be passed to this function
+ *
+ * @return
+ *  - ESP_OK   : On successfully queueing the work
+ *  - ESP_FAIL : Failure in ctrl socket
+ *  - ESP_ERR_INVALID_ARG : Null arguments
+ */
+esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *arg);
+
+/** End of Group Work Queue
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ! _ESP_HTTP_SERVER_H_ */
diff --git a/components/esp_http_server/include/http_server.h b/components/esp_http_server/include/http_server.h
new file mode 100644
index 00000000..56f73c5b
--- /dev/null
+++ b/components/esp_http_server/include/http_server.h
@@ -0,0 +1,2 @@
+#warning http_server.h has been renamed to esp_http_server.h, please update include directives
+#include "esp_http_server.h"
diff --git a/components/esp_http_server/src/esp_httpd_priv.h b/components/esp_http_server/src/esp_httpd_priv.h
new file mode 100644
index 00000000..68bc9d64
--- /dev/null
+++ b/components/esp_http_server/src/esp_httpd_priv.h
@@ -0,0 +1,531 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+#ifndef _HTTPD_PRIV_H_
+#define _HTTPD_PRIV_H_
+
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <esp_log.h>
+#include <esp_err.h>
+
+#include <esp_http_server.h>
+#include "osal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 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 */
+#define PARSER_BLOCK_SIZE  128
+
+/* Calculate the maximum size needed for the scratch buffer */
+#define HTTPD_SCRATCH_BUF  MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN)
+
+/* Formats a log string to prepend context function name */
+#define LOG_FMT(x)      "%s: " x, __func__
+
+/**
+ * @brief Thread related data for internal use
+ */
+struct thread_data {
+    othread_t handle;   /*!< Handle to thread/task */
+    enum {
+        THREAD_IDLE = 0,
+        THREAD_RUNNING,
+        THREAD_STOPPING,
+        THREAD_STOPPED,
+    } 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.
+ */
+struct sock_db {
+    int fd;                                 /*!< The file descriptor for this socket */
+    void *ctx;                              /*!< A custom context for this socket */
+    void *transport_ctx;                    /*!< A custom 'transport' context for this socket, to be used by send/recv/pending */
+    httpd_handle_t handle;                  /*!< Server handle */
+    httpd_free_ctx_fn_t free_ctx;      /*!< Function for freeing the context */
+    httpd_free_ctx_fn_t free_transport_ctx; /*!< Function for freeing the 'transport' context */
+    httpd_send_func_t send_fn;              /*!< Send function for this socket */
+    httpd_recv_func_t recv_fn;              /*!< Receive function for this socket */
+    httpd_pending_func_t pending_fn;        /*!< Pending function for this socket */
+    int64_t timestamp;                      /*!< Timestamp indicating when the socket was last used */
+    char pending_data[PARSER_BLOCK_SIZE];   /*!< Buffer for pending data to be received */
+    size_t pending_len;                     /*!< Length of pending data to be received */
+};
+
+/**
+ * @brief   Auxilary data structure for use during reception and processing
+ *          of requests and temporarily keeping responses
+ */
+struct httpd_req_aux {
+    struct sock_db *sd;                             /*!< Pointer to socket database */
+    char            scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */
+    size_t          remaining_len;                  /*!< Amount of data remaining to be fetched */
+    char           *status;                         /*!< HTTP response's status code */
+    char           *content_type;                   /*!< HTTP response's content type */
+    bool            first_chunk_sent;               /*!< Used to indicate if first chunk sent */
+    unsigned        req_hdrs_count;                 /*!< Count of total headers in request packet */
+    unsigned        resp_hdrs_count;                /*!< Count of additional headers in response packet */
+    struct resp_hdr {
+        const char *field;
+        const char *value;
+    } *resp_hdrs;                                   /*!< Additional headers in response packet */
+    struct http_parser_url url_parse_res;           /*!< URL parsing result, used for retrieving URL elements */
+};
+
+/**
+ * @brief   Server data for each instance. This is exposed publicaly as
+ *          httpd_handle_t but internal structure/members are kept private.
+ */
+struct httpd_data {
+    httpd_config_t config;                  /*!< HTTPD server configuration */
+    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 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 */
+};
+
+/******************* Group : Session Management ********************/
+/** @name Session Management
+ * Functions related to HTTP session management
+ * @{
+ */
+
+/**
+ * @brief Retrieve a session by its descriptor
+ *
+ * @param[in] hd     Server instance data
+ * @param[in] sockfd Socket FD
+ * @return pointer into the socket DB, or NULL if not found
+ */
+struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd);
+
+/**
+ * @brief Delete sessions whose FDs have became invalid.
+ *        This is a recovery strategy e.g. after select() fails.
+ *
+ * @param[in] hd    Server instance data
+ */
+void httpd_sess_delete_invalid(struct httpd_data *hd);
+
+/**
+ * @brief   Initializes an http session by resetting the sockets database.
+ *
+ * @param[in] hd    Server instance data
+ */
+void httpd_sess_init(struct httpd_data *hd);
+
+/**
+ * @brief   Starts a new session for client requesting connection and adds
+ *          it's descriptor to the socket database.
+ *
+ * @param[in] hd    Server instance data
+ * @param[in] newfd Descriptor of the new client to be added to the session.
+ *
+ * @return
+ *  - ESP_OK   : on successfully queueing the work
+ *  - ESP_FAIL : in case of control socket error while sending
+ */
+esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd);
+
+/**
+ * @brief   Processes incoming HTTP requests
+ *
+ * @param[in] hd    Server instance data
+ * @param[in] clifd Descriptor of the client from which data is to be received
+ *
+ * @return
+ *  - ESP_OK    : on successfully receiving, parsing and responding to a request
+ *  - ESP_FAIL  : in case of failure in any of the stages of processing
+ */
+esp_err_t httpd_sess_process(struct httpd_data *hd, int clifd);
+
+/**
+ * @brief   Remove client descriptor from the session / socket database
+ *          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
+ *          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.
+ *
+ * @param[in] hd    Server instance data
+ * @param[in] clifd Descriptor of the client to be removed from the session.
+ *
+ * @return
+ *  - +VE : Client descriptor preceding the one being deleted
+ *  - -1  : No descriptor preceding the one being deleted
+ */
+int httpd_sess_delete(struct httpd_data *hd, int clifd);
+
+/**
+ * @brief   Free session context
+ *
+ * @param[in] ctx     Pointer to session context
+ * @param[in] free_fn Free function to call on session context
+ */
+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
+ *          update the value of maxfd which are needed by the select function
+ *          for looking through all available sockets for incoming data.
+ *
+ * @param[in]  hd    Server instance data
+ * @param[out] fdset File descriptor set to be updated.
+ * @param[out] maxfd Maximum value among all file descriptors.
+ */
+void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd);
+
+/**
+ * @brief   Iterates through the list of client fds in the session /socket database.
+ *          Passing the value of a client fd returns the fd for the next client
+ *          in the database. In order to iterate from the beginning pass -1 as fd.
+ *
+ * @param[in] hd    Server instance data
+ * @param[in] fd    Last accessed client descriptor.
+ *                  -1 to reset iterator to start of database.
+ *
+ * @return
+ *  - +VE : Client descriptor next in the database
+ *  - -1  : End of iteration
+ */
+int httpd_sess_iterate(struct httpd_data *hd, int fd);
+
+/**
+ * @brief   Checks if session can accept another connection from new client.
+ *          If sockets database is full then this returns false.
+ *
+ * @param[in] hd  Server instance data
+ *
+ * @return True if session can accept new clients
+ */
+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
+ * 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
+ * received on the socket. This is when this function
+ * comes in use, as it checks the socket's pending data
+ * buffer.
+ *
+ * @param[in] hd  Server instance data
+ * @param[in] fd  Client descriptor
+ *
+ * @return True if there is any pending data
+ */
+bool httpd_sess_pending(struct httpd_data *hd, int fd);
+
+/**
+ * @brief   Removes the least recently used client from the session
+ *
+ * This may be useful if new clients are requesting for connection but
+ * max number of connections is reached, in which case the client which
+ * is inactive for the longest will be removed from the session.
+ *
+ * @param[in] hd  Server instance data
+ *
+ * @return
+ *  - ESP_OK    : if session closure initiated successfully
+ *  - ESP_FAIL  : if failed
+ */
+esp_err_t httpd_sess_close_lru(struct httpd_data *hd);
+
+/** End of Group : Session Management
+ * @}
+ */
+
+/****************** Group : URI Handling ********************/
+/** @name URI Handling
+ * Methods for accessing URI handlers
+ * @{
+ */
+
+/**
+ * @brief   For an HTTP request, searches through all the registered URI handlers
+ *          and invokes the appropriate one if found
+ *
+ * @param[in] hd  Server instance data for which handler needs to be invoked
+ *
+ * @return
+ *  - ESP_OK    : if handler found and executed successfully
+ *  - ESP_FAIL  : otherwise
+ */
+esp_err_t httpd_uri(struct httpd_data *hd);
+
+/**
+ * @brief   Deregister all URI handlers
+ *
+ * @param[in] hd  Server instance data
+ */
+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
+ *
+ * @return
+ *  - true  : if valid request
+ *  - false : otherwise
+ */
+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 */
+#ifdef CONFIG_HTTPD_VALIDATE_REQ
+#define httpd_valid_req(r)  httpd_validate_req_ptr(r)
+#else
+#define httpd_valid_req(r)  true
+#endif
+
+/** End of Group : URI Handling
+ * @}
+ */
+
+/****************** Group : Processing ********************/
+/** @name Processing
+ * Methods for processing HTTP requests
+ * @{
+ */
+
+/**
+ * @brief   Initiates the processing of HTTP request
+ *
+ * Receives incoming TCP packet on a socket, then parses the packet as
+ * HTTP request and fills httpd_req_t data structure with the extracted
+ * URI, headers are ready to be fetched from scratch buffer and calling
+ * http_recv() after this reads the body of the request.
+ *
+ * @param[in] hd  Server instance data
+ * @param[in] sd  Pointer to socket which is needed for receiving TCP packets.
+ *
+ * @return
+ *  - ESP_OK    : if request packet is valid
+ *  - ESP_FAIL  : otherwise
+ */
+esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd);
+
+/**
+ * @brief   For an HTTP request, resets the resources allocated for it and
+ *          purges any data left to be received
+ *
+ * @param[in] hd  Server instance data
+ *
+ * @return
+ *  - ESP_OK    : if request packet deleted and resources cleaned.
+ *  - ESP_FAIL  : otherwise.
+ */
+esp_err_t httpd_req_delete(struct httpd_data *hd);
+
+/** End of Group : Parsing
+ * @}
+ */
+
+/****************** Group : Send/Receive ********************/
+/** @name Send and Receive
+ * Methods for transmitting and receiving HTTP requests and responses
+ * @{
+ */
+
+/**
+ * @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] buf     Pointer to the buffer from where the body of the response is taken
+ * @param[in] buf_len Length of the buffer
+ *
+ * @return
+ *  - Length of data : if successful
+ *  - ESP_FAIL       : if failed
+ */
+int httpd_send(httpd_req_t *req, const char *buf, size_t buf_len);
+
+/**
+ * @brief   For receiving HTTP request data
+ *
+ * @note    The exposed API httpd_recv() is simply this function with last parameter
+ *          set as false. This function is used internally during reception and
+ *          processing of a new request. The option to halt after receiving pending
+ *          data prevents the server from requesting more data than is needed for
+ *          completing a packet in case when all the remaining part of the packet is
+ *          in the pending buffer.
+ *
+ * @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
+ *                               pending buffer
+ *
+ * @return
+ *  - Length of data : if successful
+ *  - ESP_FAIL       : if failed
+ */
+int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t buf_len, bool halt_after_pending);
+
+/**
+ * @brief   For un-receiving HTTP request data
+ *
+ * This function copies data into internal buffer pending_data so that
+ * when httpd_recv is called, it first fetches this pending data and
+ * then only starts receiving from the socket
+ *
+ * @note    If data is too large for the internal buffer then only
+ *          part of the data is unreceived, reflected in the returned
+ *          length. Make sure that such truncation is checked for and
+ *          handled properly.
+ *
+ * @param[in] req     Pointer to new HTTP request which only has the socket descriptor
+ * @param[in] buf     Pointer to the buffer from where data needs to be un-received
+ * @param[in] buf_len Length of the buffer
+ *
+ * @return  Length of data copied into pending buffer
+ */
+size_t httpd_unrecv(struct httpd_req *r, const char *buf, size_t buf_len);
+
+/**
+ * @brief   This is the low level default send function of the HTTPD. This should
+ *          NEVER be called directly. The semantics of this is exactly similar to
+ *          send() of the BSD socket API.
+ *
+ * @param[in] hd      Server instance data
+ * @param[in] sockfd  Socket descriptor for sending data
+ * @param[in] buf     Pointer to the buffer from where the body of the response is taken
+ * @param[in] buf_len Length of the buffer
+ * @param[in] flags   Flags for mode selection
+ *
+ * @return
+ *  - Length of data : if successful
+ *  - -1             : if failed (appropriate errno is set)
+ */
+int httpd_default_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags);
+
+/**
+ * @brief   This is the low level default recv function of the HTTPD. This should
+ *          NEVER be called directly. The semantics of this is exactly similar to
+ *          recv() of the BSD socket API.
+ *
+ * @param[in] hd      Server instance data
+ * @param[in] sockfd  Socket descriptor for sending data
+ * @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] flags   Flags for mode selection
+ *
+ * @return
+ *  - Length of data : if successful
+ *  - -1             : if failed (appropriate errno is set)
+ */
+int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags);
+
+/** End of Group : Send and Receive
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ! _HTTPD_PRIV_H_ */
diff --git a/components/esp_http_server/src/httpd_main.c b/components/esp_http_server/src/httpd_main.c
new file mode 100644
index 00000000..dc4cbd86
--- /dev/null
+++ b/components/esp_http_server/src/httpd_main.c
@@ -0,0 +1,403 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <assert.h>
+
+#include <esp_http_server.h>
+#include "esp_httpd_priv.h"
+#include "ctrl_sock.h"
+
+static const char *TAG = "httpd";
+
+static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd)
+{
+    /* If no space is available for new session, close the least recently used one */
+    if (hd->config.lru_purge_enable == true) {
+        if (!httpd_is_sess_available(hd)) {
+            /* Queue asynchronous closure of the least recently used session */
+            return httpd_sess_close_lru(hd);
+            /* Returning from this allowes the main server thread to process
+             * the queued asynchronous control message for closing LRU session.
+             * Since connection request hasn't been addressed yet using accept()
+             * therefore httpd_accept_conn() will be called again, but this time
+             * with space available for one session
+             */
+       }
+    }
+
+    struct sockaddr_in addr_from;
+    socklen_t addr_from_len = sizeof(addr_from);
+    int new_fd = accept(listen_fd, (struct sockaddr *)&addr_from, &addr_from_len);
+    if (new_fd < 0) {
+        ESP_LOGW(TAG, LOG_FMT("error in accept (%d)"), errno);
+        return ESP_FAIL;
+    }
+    ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd);
+
+    struct timeval tv;
+    /* Set recv timeout of this fd as per config */
+    tv.tv_sec = hd->config.recv_wait_timeout;
+    tv.tv_usec = 0;
+    setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
+
+    /* Set send timeout of this fd as per config */
+    tv.tv_sec = hd->config.send_wait_timeout;
+    tv.tv_usec = 0;
+    setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
+
+    if (ESP_OK != httpd_sess_new(hd, new_fd)) {
+        ESP_LOGW(TAG, LOG_FMT("session creation failed"));
+        close(new_fd);
+        return ESP_FAIL;
+    }
+    ESP_LOGD(TAG, LOG_FMT("complete"));
+    return ESP_OK;
+}
+
+struct httpd_ctrl_data {
+    enum httpd_ctrl_msg {
+        HTTPD_CTRL_SHUTDOWN,
+        HTTPD_CTRL_WORK,
+    } hc_msg;
+    httpd_work_fn_t hc_work;
+    void *hc_work_arg;
+};
+
+esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *arg)
+{
+    if (handle == NULL || work == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    struct httpd_data *hd = (struct httpd_data *) handle;
+    struct httpd_ctrl_data msg = {
+        .hc_msg = HTTPD_CTRL_WORK,
+        .hc_work = work,
+        .hc_work_arg = arg,
+    };
+
+    int ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
+    if (ret < 0) {
+        ESP_LOGW(TAG, LOG_FMT("failed to queue work"));
+        return ESP_FAIL;
+    }
+
+    return ESP_OK;
+}
+
+void *httpd_get_global_user_ctx(httpd_handle_t handle)
+{
+    return ((struct httpd_data *)handle)->config.global_user_ctx;
+}
+
+void *httpd_get_global_transport_ctx(httpd_handle_t handle)
+{
+    return ((struct httpd_data *)handle)->config.global_transport_ctx;
+}
+
+static void httpd_close_all_sessions(struct httpd_data *hd)
+{
+    int fd = -1;
+    while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
+        ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), fd);
+        httpd_sess_delete(hd, fd);
+        close(fd);
+    }
+}
+
+static void httpd_process_ctrl_msg(struct httpd_data *hd)
+{
+    struct httpd_ctrl_data msg;
+    int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);
+    if (ret <= 0) {
+        ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);
+        return;
+    }
+    if (ret != sizeof(msg)) {
+        ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
+        return;
+    }
+
+    switch (msg.hc_msg) {
+    case HTTPD_CTRL_WORK:
+        if (msg.hc_work) {
+            ESP_LOGD(TAG, LOG_FMT("work"));
+            (*msg.hc_work)(msg.hc_work_arg);
+        }
+        break;
+    case HTTPD_CTRL_SHUTDOWN:
+        ESP_LOGD(TAG, LOG_FMT("shutdown"));
+        hd->hd_td.status = THREAD_STOPPING;
+        break;
+    default:
+        break;
+    }
+}
+
+/* Manage in-coming connection or data requests */
+static esp_err_t httpd_server(struct httpd_data *hd)
+{
+    fd_set read_set;
+    FD_ZERO(&read_set);
+    FD_SET(hd->listen_fd, &read_set);
+    FD_SET(hd->ctrl_fd, &read_set);
+
+    int tmp_max_fd;
+    httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);
+    int maxfd = MAX(hd->listen_fd, tmp_max_fd);
+    tmp_max_fd = maxfd;
+    maxfd = MAX(hd->ctrl_fd, tmp_max_fd);
+
+    ESP_LOGD(TAG, LOG_FMT("doing select maxfd+1 = %d"), maxfd + 1);
+    int active_cnt = select(maxfd + 1, &read_set, NULL, NULL, NULL);
+    if (active_cnt < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno);
+        httpd_sess_delete_invalid(hd);
+        return ESP_OK;
+    }
+
+    /* Case0: Do we have a control message? */
+    if (FD_ISSET(hd->ctrl_fd, &read_set)) {
+        ESP_LOGD(TAG, LOG_FMT("processing ctrl message"));
+        httpd_process_ctrl_msg(hd);
+        if (hd->hd_td.status == THREAD_STOPPING) {
+            ESP_LOGD(TAG, LOG_FMT("stopping thread"));
+            return ESP_FAIL;
+        }
+    }
+
+    /* Case1: Do we have any activity on the current data
+     * sessions? */
+    int fd = -1;
+    while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
+        if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) {
+            ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
+            if (httpd_sess_process(hd, fd) != ESP_OK) {
+                ESP_LOGD(TAG, LOG_FMT("closing socket %d"), fd);
+                close(fd);
+                /* Delete session and update fd to that
+                 * preceding the one being deleted */
+                fd = httpd_sess_delete(hd, fd);
+            }
+        }
+    }
+
+    /* Case2: Do we have any incoming connection requests to
+     * process? */
+    if (FD_ISSET(hd->listen_fd, &read_set)) {
+        ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd);
+        if (httpd_accept_conn(hd, hd->listen_fd) != ESP_OK) {
+            ESP_LOGW(TAG, LOG_FMT("error accepting new connection"));
+        }
+    }
+    return ESP_OK;
+}
+
+/* The main HTTPD thread */
+static void httpd_thread(void *arg)
+{
+    int ret;
+    struct httpd_data *hd = (struct httpd_data *) arg;
+    hd->hd_td.status = THREAD_RUNNING;
+
+    ESP_LOGD(TAG, LOG_FMT("web server started"));
+    while (1) {
+        ret = httpd_server(hd);
+        if (ret != ESP_OK) {
+            break;
+        }
+    }
+
+    ESP_LOGD(TAG, LOG_FMT("web server exiting"));
+    close(hd->msg_fd);
+    cs_free_ctrl_sock(hd->ctrl_fd);
+    httpd_close_all_sessions(hd);
+    close(hd->listen_fd);
+    hd->hd_td.status = THREAD_STOPPED;
+    httpd_os_thread_delete();
+}
+
+static esp_err_t httpd_server_init(struct httpd_data *hd)
+{
+    int fd = socket(PF_INET6, SOCK_STREAM, 0);
+    if (fd < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
+        return ESP_FAIL;
+    }
+
+    struct in6_addr inaddr_any = IN6ADDR_ANY_INIT;
+    struct sockaddr_in6 serv_addr = {
+        .sin6_family  = PF_INET6,
+        .sin6_addr    = inaddr_any,
+        .sin6_port    = htons(hd->config.server_port)
+    };
+
+    int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
+    if (ret < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
+        close(fd);
+        return ESP_FAIL;
+    }
+
+    ret = listen(fd, hd->config.backlog_conn);
+    if (ret < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);
+        close(fd);
+        return ESP_FAIL;
+    }
+
+    int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);
+    if (ctrl_fd < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);
+        close(fd);
+        return ESP_FAIL;
+    }
+
+    int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (msg_fd < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);
+        close(fd);
+        close(ctrl_fd);
+        return ESP_FAIL;
+    }
+
+    hd->listen_fd = fd;
+    hd->ctrl_fd = ctrl_fd;
+    hd->msg_fd  = msg_fd;
+    return ESP_OK;
+}
+
+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");
+    }
+    return hd;
+}
+
+static void httpd_delete(struct httpd_data *hd)
+{
+    struct httpd_req_aux *ra = &hd->hd_req_aux;
+    /* Free memory of httpd instance data */
+    free(ra->resp_hdrs);
+    free(hd->hd_sd);
+
+    /* Free registered URI handlers */
+    httpd_unregister_all_uri_handlers(hd);
+    free(hd->hd_calls);
+    free(hd);
+}
+
+esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
+{
+    if (handle == NULL || config == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    struct httpd_data *hd = httpd_create(config);
+    if (hd == NULL) {
+        /* Failed to allocate memory */
+        return ESP_ERR_HTTPD_ALLOC_MEM;
+    }
+
+    if (httpd_server_init(hd) != ESP_OK) {
+        httpd_delete(hd);
+        return ESP_FAIL;
+    }
+
+    httpd_sess_init(hd);
+    if (httpd_os_thread_create(&hd->hd_td.handle, "httpd",
+                               hd->config.stack_size,
+                               hd->config.task_priority,
+                               httpd_thread, hd) != ESP_OK) {
+        /* Failed to launch task */
+        httpd_delete(hd);
+        return ESP_ERR_HTTPD_TASK;
+    }
+
+    *handle = (httpd_handle_t *)hd;
+    return ESP_OK;
+}
+
+esp_err_t httpd_stop(httpd_handle_t handle)
+{
+    struct httpd_data *hd = (struct httpd_data *) handle;
+    if (hd == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    struct httpd_ctrl_data msg;
+    memset(&msg, 0, sizeof(msg));
+    msg.hc_msg = HTTPD_CTRL_SHUTDOWN;
+    cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg));
+
+    ESP_LOGD(TAG, LOG_FMT("sent control msg to stop server"));
+    while (hd->hd_td.status != THREAD_STOPPED) {
+        httpd_os_thread_sleep(100);
+    }
+
+    /* Release global user context, if not NULL */
+    if (hd->config.global_user_ctx) {
+        if (hd->config.global_user_ctx_free_fn) {
+            hd->config.global_user_ctx_free_fn(hd->config.global_user_ctx);
+        } else {
+            free(hd->config.global_user_ctx);
+        }
+        hd->config.global_user_ctx = NULL;
+    }
+
+    /* Release global transport context, if not NULL */
+    if (hd->config.global_transport_ctx) {
+        if (hd->config.global_transport_ctx_free_fn) {
+            hd->config.global_transport_ctx_free_fn(hd->config.global_transport_ctx);
+        } else {
+            free(hd->config.global_transport_ctx);
+        }
+        hd->config.global_transport_ctx = NULL;
+    }
+
+    ESP_LOGD(TAG, LOG_FMT("server stopped"));
+    httpd_delete(hd);
+    return ESP_OK;
+}
diff --git a/components/esp_http_server/src/httpd_parse.c b/components/esp_http_server/src/httpd_parse.c
new file mode 100644
index 00000000..51843182
--- /dev/null
+++ b/components/esp_http_server/src/httpd_parse.c
@@ -0,0 +1,856 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+#include <stdlib.h>
+#include <sys/param.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <http_parser.h>
+
+#include <esp_http_server.h>
+#include "esp_httpd_priv.h"
+#include "osal.h"
+
+static const char *TAG = "httpd_parse";
+
+typedef struct {
+    /* Parser settings for http_parser_execute() */
+    http_parser_settings settings;
+
+    /* Request being parsed */
+    struct httpd_req *req;
+
+    /* Status of the parser describes the part of the
+     * HTTP request packet being processed at any moment.
+     */
+    enum {
+        PARSING_IDLE = 0,
+        PARSING_URL,
+        PARSING_HDR_FIELD,
+        PARSING_HDR_VALUE,
+        PARSING_BODY,
+        PARSING_COMPLETE,
+        PARSING_FAILED
+    } status;
+
+    /* Response error code in case of PARSING_FAILED */
+    httpd_err_resp_t error;
+
+    /* For storing last callback parameters */
+    struct {
+        const char *at;
+        size_t      length;
+    } last;
+
+    /* State variables */
+    bool   paused;          /*!< Parser is paused */
+    size_t pre_parsed;      /*!< Length of data to be skipped while parsing */
+    size_t raw_datalen;     /*!< Full length of the raw data in scratch buffer */
+} parser_data_t;
+
+static esp_err_t verify_url (http_parser *parser)
+{
+    parser_data_t *parser_data  = (parser_data_t *) parser->data;
+    struct httpd_req *r         = parser_data->req;
+    struct httpd_req_aux *ra    = r->aux;
+    struct http_parser_url *res = &ra->url_parse_res;
+
+    /* Get previous values of the parser callback arguments */
+    const char *at = parser_data->last.at;
+    size_t  length = parser_data->last.length;
+
+    if ((r->method = parser->method) < 0) {
+        ESP_LOGW(TAG, LOG_FMT("HTTP Operation not supported"));
+        parser_data->error = HTTPD_501_METHOD_NOT_IMPLEMENTED;
+        return ESP_FAIL;
+    }
+
+    if (sizeof(r->uri) < (length + 1)) {
+        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;
+    }
+
+    /* Keep URI with terminating null character. Note URI string pointed
+     * 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);
+
+    /* Make sure version is HTTP/1.1 */
+    if ((parser->http_major != 1) && (parser->http_minor != 1)) {
+        ESP_LOGW(TAG, LOG_FMT("unsupported HTTP version = %d.%d"),
+                 parser->http_major, parser->http_minor);
+        parser_data->error = HTTPD_505_VERSION_NOT_SUPPORTED;
+        return ESP_FAIL;
+    }
+
+    /* Parse URL and keep result for later */
+    http_parser_url_init(res);
+    if (http_parser_parse_url(r->uri, strlen(r->uri),
+                              r->method == HTTP_CONNECT, res)) {
+        ESP_LOGW(TAG, LOG_FMT("http_parser_parse_url failed with errno = %d"),
+                              parser->http_errno);
+        parser_data->error = HTTPD_400_BAD_REQUEST;
+        return ESP_FAIL;
+    }
+    return ESP_OK;
+}
+
+/* http_parser callback on finding url in HTTP request
+ * Will be invoked ATLEAST once every packet
+ */
+static esp_err_t cb_url(http_parser *parser,
+                        const char *at, size_t length)
+{
+    parser_data_t *parser_data = (parser_data_t *) parser->data;
+
+    if (parser_data->status == PARSING_IDLE) {
+        ESP_LOGD(TAG, LOG_FMT("message begin"));
+
+        /* Store current values of the parser callback arguments */
+        parser_data->last.at     = at;
+        parser_data->last.length = 0;
+        parser_data->status      = PARSING_URL;
+    } else if (parser_data->status != PARSING_URL) {
+        ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
+        parser_data->status = PARSING_FAILED;
+        return ESP_FAIL;
+    }
+
+    ESP_LOGD(TAG, LOG_FMT("processing url = %.*s"), length, at);
+
+    /* Update length of URL string */
+    if ((parser_data->last.length += length) > HTTPD_MAX_URI_LEN) {
+        ESP_LOGW(TAG, LOG_FMT("URI length (%d) greater than supported (%d)"),
+                 parser_data->last.length, HTTPD_MAX_URI_LEN);
+        parser_data->error = HTTPD_414_URI_TOO_LONG;
+        parser_data->status = PARSING_FAILED;
+        return ESP_FAIL;
+    }
+    return ESP_OK;
+}
+
+static esp_err_t pause_parsing(http_parser *parser, const char* at)
+{
+    parser_data_t *parser_data = (parser_data_t *) parser->data;
+    struct httpd_req *r        = parser_data->req;
+    struct httpd_req_aux *ra   = r->aux;
+
+    parser_data->pre_parsed = parser_data->raw_datalen
+                              - (at - ra->scratch);
+
+    if (parser_data->pre_parsed != httpd_unrecv(r, at, parser_data->pre_parsed)) {
+        ESP_LOGE(TAG, LOG_FMT("data too large for un-recv = %d"),
+                 parser_data->pre_parsed);
+        return ESP_FAIL;
+    }
+
+    http_parser_pause(parser, 1);
+    parser_data->paused = true;
+    ESP_LOGD(TAG, LOG_FMT("paused"));
+    return ESP_OK;
+}
+
+static size_t continue_parsing(http_parser *parser, size_t length)
+{
+    parser_data_t *data = (parser_data_t *) parser->data;
+
+    /* Part of the blk may have been parsed before
+     * so we must skip that */
+    length = MIN(length, data->pre_parsed);
+    data->pre_parsed -= length;
+    ESP_LOGD(TAG, LOG_FMT("skip pre-parsed data of size = %d"), length);
+
+    http_parser_pause(parser, 0);
+    data->paused = false;
+    ESP_LOGD(TAG, LOG_FMT("un-paused"));
+    return length;
+}
+
+/* http_parser callback on header field in HTTP request
+ * May be invoked ATLEAST once every header field
+ */
+static esp_err_t cb_header_field(http_parser *parser, const char *at, size_t length)
+{
+    parser_data_t *parser_data = (parser_data_t *) parser->data;
+    struct httpd_req *r        = parser_data->req;
+    struct httpd_req_aux *ra   = r->aux;
+
+    /* Check previous status */
+    if (parser_data->status == PARSING_URL) {
+        if (verify_url(parser) != ESP_OK) {
+            parser_data->status = PARSING_FAILED;
+            return ESP_FAIL;
+        }
+
+        ESP_LOGD(TAG, LOG_FMT("headers begin"));
+        /* Last at is set to start of scratch where headers
+         * will be received next */
+        parser_data->last.at     = ra->scratch;
+        parser_data->last.length = 0;
+        parser_data->status      = PARSING_HDR_FIELD;
+
+        /* Stop parsing for now and give control to process */
+        if (pause_parsing(parser, at) != ESP_OK) {
+            parser_data->status = PARSING_FAILED;
+            return ESP_FAIL;
+        }
+    } else if (parser_data->status == PARSING_HDR_VALUE) {
+        /* NULL terminate last header (key: value) pair */
+        size_t offset = parser_data->last.at - ra->scratch;
+        ra->scratch[offset + parser_data->last.length] = '\0';
+
+        /* Store current values of the parser callback arguments */
+        parser_data->last.at     = at;
+        parser_data->last.length = 0;
+        parser_data->status      = PARSING_HDR_FIELD;
+    } else if (parser_data->status != PARSING_HDR_FIELD) {
+        ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
+        parser_data->status = PARSING_FAILED;
+        return ESP_FAIL;
+    }
+
+    ESP_LOGD(TAG, LOG_FMT("processing field = %.*s"), length, at);
+
+    /* Update length of header string */
+    parser_data->last.length += length;
+    return ESP_OK;
+}
+
+/* http_parser callback on header value in HTTP request.
+ * May be invoked ATLEAST once every header value
+ */
+static esp_err_t cb_header_value(http_parser *parser, const char *at, size_t length)
+{
+    parser_data_t *parser_data = (parser_data_t *) parser->data;
+    struct httpd_req *r        = parser_data->req;
+    struct httpd_req_aux *ra   = r->aux;
+
+    /* Check previous status */
+    if (parser_data->status == PARSING_HDR_FIELD) {
+        /* Store current values of the parser callback arguments */
+        parser_data->last.at     = at;
+        parser_data->last.length = 0;
+        parser_data->status      = PARSING_HDR_VALUE;
+        /* Increment header count */
+        ra->req_hdrs_count++;
+    } else if (parser_data->status != PARSING_HDR_VALUE) {
+        ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
+        parser_data->status = PARSING_FAILED;
+        return ESP_FAIL;
+    }
+
+    ESP_LOGD(TAG, LOG_FMT("processing value = %.*s"), length, at);
+
+    /* Update length of header string */
+    parser_data->last.length += length;
+    return ESP_OK;
+}
+
+/* http_parser callback on completing headers in HTTP request.
+ * Will be invoked ONLY once every packet
+ */
+static esp_err_t cb_headers_complete(http_parser *parser)
+{
+    parser_data_t *parser_data = (parser_data_t *) parser->data;
+    struct httpd_req *r        = parser_data->req;
+    struct httpd_req_aux *ra   = r->aux;
+
+    /* Check previous status */
+    if (parser_data->status == PARSING_URL) {
+        ESP_LOGD(TAG, LOG_FMT("no headers"));
+        if (verify_url(parser) != ESP_OK) {
+            parser_data->status = PARSING_FAILED;
+            return ESP_FAIL;
+        }
+    } else if (parser_data->status == PARSING_HDR_VALUE) {
+        /* NULL terminate last header (key: value) pair */
+        size_t offset = parser_data->last.at - ra->scratch;
+        ra->scratch[offset + parser_data->last.length] = '\0';
+
+        /* Reach end of last header */
+        parser_data->last.at += parser_data->last.length;
+    } else {
+        ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
+        parser_data->status = PARSING_FAILED;
+        return ESP_FAIL;
+    }
+
+    /* 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);
+
+    ESP_LOGD(TAG, LOG_FMT("bytes read     = %d"),  parser->nread);
+    ESP_LOGD(TAG, LOG_FMT("content length = %zu"), r->content_len);
+
+    if (parser->upgrade) {
+        ESP_LOGW(TAG, LOG_FMT("upgrade from HTTP not supported"));
+        parser_data->error = HTTPD_XXX_UPGRADE_NOT_SUPPORTED;
+        parser_data->status = PARSING_FAILED;
+        return ESP_FAIL;
+    }
+
+    parser_data->status = PARSING_BODY;
+    ra->remaining_len = r->content_len;
+    return ESP_OK;
+}
+
+/* Last http_parser callback if body present in HTTP request.
+ * Will be invoked ONLY once every packet
+ */
+static esp_err_t cb_on_body(http_parser *parser, const char *at, size_t length)
+{
+    parser_data_t *parser_data = (parser_data_t *) parser->data;
+
+    /* Check previous status */
+    if (parser_data->status != PARSING_BODY) {
+        ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
+        parser_data->status = PARSING_FAILED;
+        return ESP_FAIL;
+    }
+
+    /* Pause parsing so that if part of another packet
+     * is in queue then it doesn't get parsed, which
+     * may reset the parser state and cause current
+     * request packet to be lost */
+    if (pause_parsing(parser, at) != ESP_OK) {
+        parser_data->status = PARSING_FAILED;
+        return ESP_FAIL;
+    }
+
+    parser_data->last.at     = 0;
+    parser_data->last.length = 0;
+    parser_data->status      = PARSING_COMPLETE;
+    ESP_LOGD(TAG, LOG_FMT("body begins"));
+    return ESP_OK;
+}
+
+/* Last http_parser callback if body absent in HTTP request.
+ * Will be invoked ONLY once every packet
+ */
+static esp_err_t cb_no_body(http_parser *parser)
+{
+    parser_data_t *parser_data = (parser_data_t *) parser->data;
+    const char* at             = parser_data->last.at;
+
+    /* Check previous status */
+    if (parser_data->status == PARSING_URL) {
+        ESP_LOGD(TAG, LOG_FMT("no headers"));
+        if (verify_url(parser) != ESP_OK) {
+            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->status = PARSING_FAILED;
+        return ESP_FAIL;
+    }
+
+    /* Get end of packet */
+    at += strlen("\r\n\r\n");
+
+    /* Pause parsing so that if part of another packet
+     * is in queue then it doesn't get parsed, which
+     * may reset the parser state and cause current
+     * request packet to be lost */
+    if (pause_parsing(parser, at) != ESP_OK) {
+        parser_data->status = PARSING_FAILED;
+        return ESP_FAIL;
+    }
+
+    parser_data->last.at     = 0;
+    parser_data->last.length = 0;
+    parser_data->status      = PARSING_COMPLETE;
+    ESP_LOGD(TAG, LOG_FMT("message complete"));
+    return ESP_OK;
+}
+
+static int read_block(httpd_req_t *req, size_t offset, size_t length)
+{
+    struct httpd_req_aux *raux  = req->aux;
+
+    /* Limits the read to scratch buffer size */
+    size_t buf_len = MIN(length, (sizeof(raux->scratch) - offset));
+    if (buf_len == 0) {
+        return 0;
+    }
+
+    /* Receive data into buffer. If data is pending (from unrecv) then return
+     * 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"));
+        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"));
+        return -1;
+    }
+
+    ESP_LOGD(TAG, LOG_FMT("received HTTP request block size = %d"), nbytes);
+    return nbytes;
+}
+
+static int parse_block(http_parser *parser, size_t offset, size_t length)
+{
+    parser_data_t        *data  = (parser_data_t *)(parser->data);
+    httpd_req_t          *req   = data->req;
+    struct httpd_req_aux *raux  = req->aux;
+    size_t nparsed = 0;
+
+    if (!length) {
+        ESP_LOGW(TAG, LOG_FMT("response uri/header too big"));
+        switch (data->status) {
+            case PARSING_URL:
+                data->error = HTTPD_414_URI_TOO_LONG;
+                break;
+            case PARSING_HDR_FIELD:
+            case PARSING_HDR_VALUE:
+                data->error = HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE;
+            default:
+                break;
+        }
+        data->status = PARSING_FAILED;
+        return -1;
+    }
+
+    /* Unpause the parsing if paused */
+    if (data->paused) {
+        nparsed = continue_parsing(parser, length);
+        length -= nparsed;
+        offset += nparsed;
+        if (!length) {
+            return nparsed;
+        }
+    }
+
+    /* Execute http_parser */
+    nparsed = http_parser_execute(parser, &data->settings,
+                                  raux->scratch + offset, length);
+
+    /* Check state */
+    if (data->status == PARSING_FAILED) {
+        ESP_LOGW(TAG, LOG_FMT("parsing failed"));
+        return -1;
+    } else if (data->paused) {
+        /* Keep track of parsed data to be skipped
+         * during next parsing cycle */
+        data->pre_parsed -= (length - nparsed);
+        return 0;
+    } else if (nparsed != length) {
+        /* http_parser error */
+        data->status = PARSING_FAILED;
+        data->error  = HTTPD_400_BAD_REQUEST;
+        ESP_LOGW(TAG, LOG_FMT("incomplete (%d/%d) with parser error = %d"),
+                 nparsed, length, parser->http_errno);
+        return -1;
+    }
+
+    /* Continue parsing this section of HTTP request packet */
+    ESP_LOGD(TAG, LOG_FMT("parsed block size = %d"), offset + nparsed);
+    return offset + nparsed;
+}
+
+static void parse_init(httpd_req_t *r, http_parser *parser, parser_data_t *data)
+{
+    /* Initialize parser data */
+    memset(data, 0, sizeof(parser_data_t));
+    data->req = r;
+
+    /* Initialize parser */
+    http_parser_init(parser, HTTP_REQUEST);
+    parser->data = (void *)data;
+
+    /* Initialize parser settings */
+    http_parser_settings_init(&data->settings);
+
+    /* Set parser callbacks */
+    data->settings.on_url              = cb_url;
+    data->settings.on_header_field     = cb_header_field;
+    data->settings.on_header_value     = cb_header_value;
+    data->settings.on_headers_complete = cb_headers_complete;
+    data->settings.on_body             = cb_on_body;
+    data->settings.on_message_complete = cb_no_body;
+}
+
+/* Function that receives TCP data and runs parser on it
+ */
+static esp_err_t httpd_parse_req(struct httpd_data *hd)
+{
+    httpd_req_t *r = &hd->hd_req;
+    int blk_len,  offset;
+    http_parser   parser;
+    parser_data_t parser_data;
+
+    /* Initialize parser */
+    parse_init(r, &parser, &parser_data);
+
+    /* Set offset to start of scratch buffer */
+    offset = 0;
+    do {
+        /* Read block into scratch buffer */
+        if ((blk_len = read_block(r, offset, PARSER_BLOCK_SIZE)) < 0) {
+            /* Return error to close socket */
+            return ESP_FAIL;
+        }
+
+        /* This is used by the callbacks to track
+         * data usage of the buffer */
+        parser_data.raw_datalen = blk_len + offset;
+
+        /* 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);
+        }
+    } while (parser_data.status != PARSING_COMPLETE);
+
+    ESP_LOGD(TAG, LOG_FMT("parsing complete"));
+    return httpd_uri(hd);
+}
+
+static void init_req(httpd_req_t *r, httpd_config_t *config)
+{
+    r->handle = 0;
+    r->method = 0;
+    memset((char*)r->uri, 0, sizeof(r->uri));
+    r->content_len = 0;
+    r->aux = 0;
+    r->user_ctx = 0;
+    r->sess_ctx = 0;
+    r->free_ctx = 0;
+}
+
+static void init_req_aux(struct httpd_req_aux *ra, httpd_config_t *config)
+{
+    ra->sd = 0;
+    memset(ra->scratch, 0, sizeof(ra->scratch));
+    ra->remaining_len = 0;
+    ra->status = 0;
+    ra->content_type = 0;
+    ra->first_chunk_sent = 0;
+    ra->req_hdrs_count = 0;
+    ra->resp_hdrs_count = 0;
+    memset(ra->resp_hdrs, 0, config->max_resp_headers * sizeof(struct resp_hdr));
+}
+
+static void httpd_req_cleanup(httpd_req_t *r)
+{
+    struct httpd_req_aux *ra = r->aux;
+
+    /* Retrieve session info from the request into the socket database */
+    if (ra->sd->ctx != r->sess_ctx) {
+        /* Free previous context */
+        httpd_sess_free_ctx(ra->sd->ctx, ra->sd->free_ctx);
+        ra->sd->ctx = r->sess_ctx;
+    }
+    ra->sd->free_ctx = r->free_ctx;
+
+    /* Clear out the request and request_aux structures */
+    ra->sd = NULL;
+    r->handle = NULL;
+    r->aux = NULL;
+}
+
+/* Function that processes incoming TCP data and
+ * updates the http request data httpd_req_t
+ */
+esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd)
+{
+    httpd_req_t *r = &hd->hd_req;
+    init_req(r, &hd->config);
+    init_req_aux(&hd->hd_req_aux, &hd->config);
+    r->handle = hd;
+    r->aux = &hd->hd_req_aux;
+    /* Associate the request to the socket */
+    struct httpd_req_aux *ra = r->aux;
+    ra->sd = sd;
+    /* Set defaults */
+    ra->status = (char *)HTTPD_200;
+    ra->content_type = (char *)HTTPD_TYPE_TEXT;
+    ra->first_chunk_sent = false;
+    /* Copy session info to the request */
+    r->sess_ctx = sd->ctx;
+    r->free_ctx = sd->free_ctx;
+    /* Parse request */
+    esp_err_t err = httpd_parse_req(hd);
+    if (err != ESP_OK) {
+        httpd_req_cleanup(r);
+    }
+    return err;
+}
+
+/* Function that resets the http request data
+ */
+esp_err_t httpd_req_delete(struct httpd_data *hd)
+{
+    httpd_req_t *r = &hd->hd_req;
+    struct httpd_req_aux *ra = r->aux;
+
+    /* Finish off reading any pending/leftover data */
+    while (ra->remaining_len) {
+        /* Any length small enough not to overload the stack, but large
+         * enough to finish off the buffers fast
+         */
+        char dummy[32];
+        int recv_len = MIN(sizeof(dummy) - 1, ra->remaining_len);
+        int ret = httpd_req_recv(r, dummy, recv_len);
+        if (ret <  0) {
+            httpd_req_cleanup(r);
+            return ESP_FAIL;
+        }
+
+        dummy[ret] = '\0';
+        ESP_LOGD(TAG, LOG_FMT("purging data : %s"), dummy);
+    }
+
+    httpd_req_cleanup(r);
+    return ESP_OK;
+}
+
+/* Validates the request to prevent users from calling APIs, that are to
+ * be called only inside URI handler, outside the handler context
+ */
+bool httpd_validate_req_ptr(httpd_req_t *r)
+{
+    if (r) {
+        struct httpd_data *hd = (struct httpd_data *) r->handle;
+        if (hd) {
+            /* Check if this function is running in the context of
+             * the correct httpd server thread */
+            if (httpd_os_thread_handle() == hd->hd_td.handle) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+/* Helper function to get a URL query tag from a query string of the type param1=val1&param2=val2 */
+esp_err_t httpd_query_key_value(const char *qry_str, const char *key, char *val, size_t val_size)
+{
+    if (qry_str == NULL || key == NULL || val == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    const char   *qry_ptr = qry_str;
+    const size_t  buf_len = val_size;
+
+    while (strlen(qry_ptr)) {
+        /* Search for the '=' character. Else, it would mean
+         * that the parameter is invalid */
+        const char *val_ptr = strchr(qry_ptr, '=');
+        if (!val_ptr) {
+            break;
+        }
+        size_t offset = val_ptr - qry_ptr;
+
+        /* If the key, does not match, continue searching.
+         * Compare lengths first as key from url is not
+         * null terminated (has '=' in the end) */
+        if ((offset != strlen(key)) ||
+            (strncasecmp(qry_ptr, key, offset))) {
+            /* Get the name=val string. Multiple name=value pairs
+             * are separated by '&' */
+            qry_ptr = strchr(val_ptr, '&');
+            if (!qry_ptr) {
+                break;
+            }
+            qry_ptr++;
+            continue;
+        }
+
+        /* Locate start of next query */
+        qry_ptr = strchr(++val_ptr, '&');
+        /* Or this could be the last query, in which
+         * case get to the end of query string */
+        if (!qry_ptr) {
+            qry_ptr = val_ptr + strlen(val_ptr);
+        }
+
+        /* Update value length, including one byte for null */
+        val_size = qry_ptr - val_ptr + 1;
+
+        /* Copy value to the caller's buffer. */
+        strlcpy(val, val_ptr, MIN(val_size, buf_len));
+
+        /* If buffer length is smaller than needed, return truncation error */
+        if (buf_len < val_size) {
+            return ESP_ERR_HTTPD_RESULT_TRUNC;
+        }
+        return ESP_OK;
+    }
+    ESP_LOGD(TAG, LOG_FMT("key %s not found"), key);
+    return ESP_ERR_NOT_FOUND;
+}
+
+size_t httpd_req_get_url_query_len(httpd_req_t *r)
+{
+    if (r == NULL) {
+        return 0;
+    }
+
+    if (!httpd_valid_req(r)) {
+        return 0;
+    }
+
+    struct httpd_req_aux   *ra  = r->aux;
+    struct http_parser_url *res = &ra->url_parse_res;
+
+    /* Check if query field is present in the URL */
+    if (res->field_set & (1 << UF_QUERY)) {
+        return res->field_data[UF_QUERY].len;
+    }
+    return 0;
+}
+
+esp_err_t httpd_req_get_url_query_str(httpd_req_t *r, char *buf, size_t buf_len)
+{
+    if (r == NULL || buf == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!httpd_valid_req(r)) {
+        return ESP_ERR_HTTPD_INVALID_REQ;
+    }
+
+    struct httpd_req_aux   *ra  = r->aux;
+    struct http_parser_url *res = &ra->url_parse_res;
+
+    /* Check if query field is present in the URL */
+    if (res->field_set & (1 << UF_QUERY)) {
+        const char *qry = r->uri + res->field_data[UF_QUERY].off;
+
+        /* Minimum required buffer len for keeping
+         * null terminated query string */
+        size_t min_buf_len = res->field_data[UF_QUERY].len + 1;
+
+        strlcpy(buf, qry, MIN(buf_len, min_buf_len));
+        if (buf_len < min_buf_len) {
+            return ESP_ERR_HTTPD_RESULT_TRUNC;
+        }
+        return ESP_OK;
+    }
+    return ESP_ERR_NOT_FOUND;
+}
+
+/* Get the length of the value string of a header request field */
+size_t httpd_req_get_hdr_value_len(httpd_req_t *r, const char *field)
+{
+    if (r == NULL || field == NULL) {
+        return 0;
+    }
+
+    if (!httpd_valid_req(r)) {
+        return 0;
+    }
+
+    struct httpd_req_aux *ra = r->aux;
+    const char   *hdr_ptr = ra->scratch;         /*!< Request headers are kept in scratch buffer */
+    unsigned      count   = ra->req_hdrs_count;  /*!< Count set during parsing  */
+
+    while (count--) {
+        /* Search for the ':' character. Else, it would mean
+         * that the field is invalid
+         */
+        const char *val_ptr = strchr(hdr_ptr, ':');
+        if (!val_ptr) {
+            break;
+        }
+
+        /* If the field, does not match, continue searching.
+         * Compare lengths first as field from header is not
+         * null terminated (has ':' in the end).
+         */
+        if ((val_ptr - hdr_ptr != strlen(field)) ||
+            (strncasecmp(hdr_ptr, field, strlen(field)))) {
+            hdr_ptr += strlen(hdr_ptr) + strlen("\r\n");
+            continue;
+        }
+        /* Skip ':' */
+        val_ptr++;
+
+        /* Skip preceding space */
+        while ((*val_ptr != '\0') && (*val_ptr == ' ')) {
+            val_ptr++;
+        }
+        return strlen(val_ptr);
+    }
+    return 0;
+}
+
+/* Get the value of a field from the request headers */
+esp_err_t httpd_req_get_hdr_value_str(httpd_req_t *r, const char *field, char *val, size_t val_size)
+{
+    if (r == NULL || field == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!httpd_valid_req(r)) {
+        return ESP_ERR_HTTPD_INVALID_REQ;
+    }
+
+    struct httpd_req_aux *ra = r->aux;
+    const char   *hdr_ptr = ra->scratch;         /*!< Request headers are kept in scratch buffer */
+    unsigned     count    = ra->req_hdrs_count;  /*!< Count set during parsing  */
+    const size_t buf_len  = val_size;
+
+    while (count--) {
+        /* Search for the ':' character. Else, it would mean
+         * that the field is invalid
+         */
+        const char *val_ptr = strchr(hdr_ptr, ':');
+        if (!val_ptr) {
+            break;
+        }
+
+        /* If the field, does not match, continue searching.
+         * Compare lengths first as field from header is not
+         * null terminated (has ':' in the end).
+         */
+        if ((val_ptr - hdr_ptr != strlen(field)) ||
+            (strncasecmp(hdr_ptr, field, strlen(field)))) {
+            hdr_ptr += strlen(hdr_ptr) + strlen("\r\n");
+            continue;
+        }
+
+        /* Skip ':' */
+        val_ptr++;
+
+        /* Skip preceding space */
+        while ((*val_ptr != '\0') && (*val_ptr == ' ')) {
+            val_ptr++;
+        }
+
+        /* Get the NULL terminated value and copy it to the caller's buffer. */
+        strlcpy(val, val_ptr, buf_len);
+
+        /* Update value length, including one byte for null */
+        val_size = strlen(val_ptr) + 1;
+
+        /* If buffer length is smaller than needed, return truncation error */
+        if (buf_len < val_size) {
+            return ESP_ERR_HTTPD_RESULT_TRUNC;
+        }
+        return ESP_OK;
+    }
+    return ESP_ERR_NOT_FOUND;
+}
diff --git a/components/esp_http_server/src/httpd_sess.c b/components/esp_http_server/src/httpd_sess.c
new file mode 100644
index 00000000..8c0601fd
--- /dev/null
+++ b/components/esp_http_server/src/httpd_sess.c
@@ -0,0 +1,386 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+#include <stdlib.h>
+#include <esp_log.h>
+#include <esp_err.h>
+
+#include <esp_http_server.h>
+#include "esp_httpd_priv.h"
+
+static const char *TAG = "httpd_sess";
+
+bool httpd_is_sess_available(struct httpd_data *hd)
+{
+    int i;
+    for (i = 0; i < hd->config.max_open_sockets; i++) {
+        if (hd->hd_sd[i].fd == -1) {
+            return true;
+        }
+    }
+    return false;
+}
+
+struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
+{
+    if (hd == NULL) {
+        return NULL;
+    }
+
+    /* Check if called inside a request handler, and the
+     * session sockfd in use is same as the parameter */
+    if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
+        /* Just return the pointer to the sock_db
+         * corresponding to the request */
+        return hd->hd_req_aux.sd;
+    }
+
+    int i;
+    for (i = 0; i < hd->config.max_open_sockets; i++) {
+        if (hd->hd_sd[i].fd == sockfd) {
+            return &hd->hd_sd[i];
+        }
+    }
+    return NULL;
+}
+
+esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
+{
+    ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd);
+
+    if (httpd_sess_get(hd, newfd)) {
+        ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd);
+        return ESP_FAIL;
+    }
+
+    int i;
+    for (i = 0; i < hd->config.max_open_sockets; i++) {
+        if (hd->hd_sd[i].fd == -1) {
+            memset(&hd->hd_sd[i], 0, sizeof(hd->hd_sd[i]));
+            hd->hd_sd[i].fd = newfd;
+            hd->hd_sd[i].handle = (httpd_handle_t) hd;
+            hd->hd_sd[i].send_fn = httpd_default_send;
+            hd->hd_sd[i].recv_fn = httpd_default_recv;
+
+            /* Call user-defined session opening function */
+            if (hd->config.open_fn) {
+                esp_err_t ret = hd->config.open_fn(hd, hd->hd_sd[i].fd);
+                if (ret != ESP_OK) return ret;
+            }
+            return ESP_OK;
+        }
+    }
+    ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
+    return ESP_FAIL;
+}
+
+void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn)
+{
+    if (ctx) {
+        if (free_fn) {
+            free_fn(ctx);
+        } else {
+            free(ctx);
+        }
+    }
+}
+
+void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
+{
+    struct sock_db *sd = httpd_sess_get(handle, sockfd);
+    if (sd == NULL) {
+        return NULL;
+    }
+
+    /* Check if the function has been called from inside a
+     * request handler, in which case fetch the context from
+     * the httpd_req_t structure */
+    struct httpd_data *hd = (struct httpd_data *) handle;
+    if (hd->hd_req_aux.sd == sd) {
+        return hd->hd_req.sess_ctx;
+    }
+
+    return sd->ctx;
+}
+
+void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
+{
+    struct sock_db *sd = httpd_sess_get(handle, sockfd);
+    if (sd == NULL) {
+        return;
+    }
+
+    /* Check if the function has been called from inside a
+     * request handler, in which case set the context inside
+     * the httpd_req_t structure */
+    struct httpd_data *hd = (struct httpd_data *) handle;
+    if (hd->hd_req_aux.sd == sd) {
+        if (hd->hd_req.sess_ctx != ctx) {
+            /* Don't free previous context if it is in sockdb
+             * as it will be freed inside httpd_req_cleanup() */
+            if (sd->ctx != hd->hd_req.sess_ctx) {
+                /* Free previous context */
+                httpd_sess_free_ctx(hd->hd_req.sess_ctx, hd->hd_req.free_ctx);
+            }
+            hd->hd_req.sess_ctx = ctx;
+        }
+        hd->hd_req.free_ctx = free_fn;
+        return;
+    }
+
+    /* Else set the context inside the sock_db structure */
+    if (sd->ctx != ctx) {
+        /* Free previous context */
+        httpd_sess_free_ctx(sd->ctx, sd->free_ctx);
+        sd->ctx = ctx;
+    }
+    sd->free_ctx = free_fn;
+}
+
+void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
+{
+    struct sock_db *sd = httpd_sess_get(handle, sockfd);
+    if (sd == NULL) {
+        return NULL;
+    }
+
+    return sd->transport_ctx;
+}
+
+void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
+{
+    struct sock_db *sd = httpd_sess_get(handle, sockfd);
+    if (sd == NULL) {
+        return;
+    }
+
+    if (sd->transport_ctx != ctx) {
+        /* Free previous transport context */
+        httpd_sess_free_ctx(sd->transport_ctx, sd->free_transport_ctx);
+        sd->transport_ctx = ctx;
+    }
+    sd->free_transport_ctx = free_fn;
+}
+
+void httpd_sess_set_descriptors(struct httpd_data *hd,
+                                fd_set *fdset, int *maxfd)
+{
+    int i;
+    *maxfd = -1;
+    for (i = 0; i < hd->config.max_open_sockets; i++) {
+        if (hd->hd_sd[i].fd != -1) {
+            FD_SET(hd->hd_sd[i].fd, fdset);
+            if (hd->hd_sd[i].fd > *maxfd) {
+                *maxfd = hd->hd_sd[i].fd;
+            }
+        }
+    }
+}
+
+/** Check if a FD is valid */
+static int fd_is_valid(int fd)
+{
+    return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
+}
+
+void httpd_sess_delete_invalid(struct httpd_data *hd)
+{
+    for (int i = 0; i < hd->config.max_open_sockets; i++) {
+        if (hd->hd_sd[i].fd != -1 && !fd_is_valid(hd->hd_sd[i].fd)) {
+            ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), hd->hd_sd[i].fd);
+            httpd_sess_delete(hd, hd->hd_sd[i].fd);
+        }
+    }
+}
+
+int httpd_sess_delete(struct httpd_data *hd, int fd)
+{
+    ESP_LOGD(TAG, LOG_FMT("fd = %d"), fd);
+    int i;
+    int pre_sess_fd = -1;
+    for (i = 0; i < hd->config.max_open_sockets; i++) {
+        if (hd->hd_sd[i].fd == fd) {
+            /* global close handler */
+            if (hd->config.close_fn) {
+                hd->config.close_fn(hd, fd);
+            }
+
+            /* release 'user' context */
+            if (hd->hd_sd[i].ctx) {
+                if (hd->hd_sd[i].free_ctx) {
+                    hd->hd_sd[i].free_ctx(hd->hd_sd[i].ctx);
+                } else {
+                    free(hd->hd_sd[i].ctx);
+                }
+                hd->hd_sd[i].ctx = NULL;
+                hd->hd_sd[i].free_ctx = NULL;
+            }
+
+            /* release 'transport' context */
+            if (hd->hd_sd[i].transport_ctx) {
+                if (hd->hd_sd[i].free_transport_ctx) {
+                    hd->hd_sd[i].free_transport_ctx(hd->hd_sd[i].transport_ctx);
+                } else {
+                    free(hd->hd_sd[i].transport_ctx);
+                }
+                hd->hd_sd[i].transport_ctx = NULL;
+                hd->hd_sd[i].free_transport_ctx = NULL;
+            }
+
+            /* mark session slot as available */
+            hd->hd_sd[i].fd = -1;
+            break;
+        } else if (hd->hd_sd[i].fd != -1) {
+            /* Return the fd just preceding the one being
+             * deleted so that iterator can continue from
+             * the correct fd */
+            pre_sess_fd = hd->hd_sd[i].fd;
+        }
+    }
+    return pre_sess_fd;
+}
+
+void httpd_sess_init(struct httpd_data *hd)
+{
+    int i;
+    for (i = 0; i < hd->config.max_open_sockets; i++) {
+        hd->hd_sd[i].fd = -1;
+        hd->hd_sd[i].ctx = NULL;
+    }
+}
+
+bool httpd_sess_pending(struct httpd_data *hd, int fd)
+{
+    struct sock_db *sd = httpd_sess_get(hd, fd);
+    if (! sd) {
+        return ESP_FAIL;
+    }
+
+    if (sd->pending_fn) {
+        // test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
+        // this should check e.g. for the SSL data buffer
+        if (sd->pending_fn(hd, fd) > 0) return true;
+    }
+
+    return (sd->pending_len != 0);
+}
+
+/* This MUST return ESP_OK on successful execution. If any other
+ * value is returned, everything related to this socket will be
+ * cleaned up and the socket will be closed.
+ */
+esp_err_t httpd_sess_process(struct httpd_data *hd, int newfd)
+{
+    struct sock_db *sd = httpd_sess_get(hd, newfd);
+    if (! sd) {
+        return ESP_FAIL;
+    }
+
+    ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
+    if (httpd_req_new(hd, sd) != ESP_OK) {
+        return ESP_FAIL;
+    }
+    ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
+    if (httpd_req_delete(hd) != ESP_OK) {
+        return ESP_FAIL;
+    }
+    ESP_LOGD(TAG, LOG_FMT("success"));
+    sd->timestamp = httpd_os_get_timestamp();
+    return ESP_OK;
+}
+
+esp_err_t httpd_sess_update_timestamp(httpd_handle_t handle, int sockfd)
+{
+    if (handle == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    /* Search for the socket database entry */
+    struct httpd_data *hd = (struct httpd_data *) handle;
+    int i;
+    for (i = 0; i < hd->config.max_open_sockets; i++) {
+        if (hd->hd_sd[i].fd == sockfd) {
+            hd->hd_sd[i].timestamp = httpd_os_get_timestamp();
+            return ESP_OK;
+        }
+    }
+    return ESP_ERR_NOT_FOUND;
+}
+
+esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
+{
+    int64_t timestamp = INT64_MAX;
+    int lru_fd = -1;
+    int i;
+    for (i = 0; i < hd->config.max_open_sockets; i++) {
+        /* If a descriptor is -1, there is no need to close any session.
+         * So, we can return from here, without finding the Least Recently Used
+         * session
+         */
+        if (hd->hd_sd[i].fd == -1) {
+            return ESP_OK;
+        }
+        if (hd->hd_sd[i].timestamp < timestamp) {
+            timestamp = hd->hd_sd[i].timestamp;
+            lru_fd = hd->hd_sd[i].fd;
+        }
+    }
+    ESP_LOGD(TAG, LOG_FMT("fd = %d"), lru_fd);
+    return httpd_sess_trigger_close(hd, lru_fd);
+}
+
+int httpd_sess_iterate(struct httpd_data *hd, int start_fd)
+{
+    int start_index = 0;
+    int i;
+
+    if (start_fd != -1) {
+        /* Take our index to where this fd is stored */
+        for (i = 0; i < hd->config.max_open_sockets; i++) {
+            if (hd->hd_sd[i].fd == start_fd) {
+                start_index = i + 1;
+                break;
+            }
+        }
+    }
+
+    for (i = start_index; i < hd->config.max_open_sockets; i++) {
+        if (hd->hd_sd[i].fd != -1) {
+            return hd->hd_sd[i].fd;
+        }
+    }
+    return -1;
+}
+
+static void httpd_sess_close(void *arg)
+{
+    struct sock_db *sock_db = (struct sock_db *)arg;
+    if (sock_db) {
+        int fd = sock_db->fd;
+        struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
+        httpd_sess_delete(hd, fd);
+        close(fd);
+    }
+}
+
+esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
+{
+    struct sock_db *sock_db = httpd_sess_get(handle, sockfd);
+    if (sock_db) {
+        return httpd_queue_work(handle, httpd_sess_close, sock_db);
+    }
+
+    return ESP_ERR_NOT_FOUND;
+}
diff --git a/components/esp_http_server/src/httpd_txrx.c b/components/esp_http_server/src/httpd_txrx.c
new file mode 100644
index 00000000..69a5ba00
--- /dev/null
+++ b/components/esp_http_server/src/httpd_txrx.c
@@ -0,0 +1,553 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+#include <errno.h>
+#include <esp_log.h>
+#include <esp_err.h>
+
+#include <esp_http_server.h>
+#include "esp_httpd_priv.h"
+
+static const char *TAG = "httpd_txrx";
+
+esp_err_t httpd_sess_set_send_override(httpd_handle_t hd, int sockfd, httpd_send_func_t send_func)
+{
+    struct sock_db *sess = httpd_sess_get(hd, sockfd);
+    if (!sess) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    sess->send_fn = send_func;
+    return ESP_OK;
+}
+
+esp_err_t httpd_sess_set_recv_override(httpd_handle_t hd, int sockfd, httpd_recv_func_t recv_func)
+{
+    struct sock_db *sess = httpd_sess_get(hd, sockfd);
+    if (!sess) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    sess->recv_fn = recv_func;
+    return ESP_OK;
+}
+
+esp_err_t httpd_sess_set_pending_override(httpd_handle_t hd, int sockfd, httpd_pending_func_t pending_func)
+{
+    struct sock_db *sess = httpd_sess_get(hd, sockfd);
+    if (!sess) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    sess->pending_fn = pending_func;
+    return ESP_OK;
+}
+
+int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len)
+{
+    if (r == NULL || buf == NULL) {
+        return HTTPD_SOCK_ERR_INVALID;
+    }
+
+    if (!httpd_valid_req(r)) {
+        return HTTPD_SOCK_ERR_INVALID;
+    }
+
+    struct httpd_req_aux *ra = r->aux;
+    int ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0);
+    if (ret < 0) {
+        ESP_LOGD(TAG, LOG_FMT("error in send_fn"));
+        return ret;
+    }
+    return ret;
+}
+
+static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len)
+{
+    struct httpd_req_aux *ra = r->aux;
+    int ret;
+
+    while (buf_len > 0) {
+        ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0);
+        if (ret < 0) {
+            ESP_LOGD(TAG, LOG_FMT("error in send_fn"));
+            return ESP_FAIL;
+        }
+        ESP_LOGD(TAG, LOG_FMT("sent = %d"), ret);
+        buf     += ret;
+        buf_len -= ret;
+    }
+    return ESP_OK;
+}
+
+static size_t httpd_recv_pending(httpd_req_t *r, char *buf, size_t buf_len)
+{
+    struct httpd_req_aux *ra = r->aux;
+    size_t offset = sizeof(ra->sd->pending_data) - ra->sd->pending_len;
+
+    /* buf_len must not be greater than remaining_len */
+    buf_len = MIN(ra->sd->pending_len, buf_len);
+    memcpy(buf, ra->sd->pending_data + offset, buf_len);
+
+    ra->sd->pending_len -= buf_len;
+    return buf_len;
+}
+
+int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t buf_len, bool halt_after_pending)
+{
+    ESP_LOGD(TAG, LOG_FMT("requested length = %d"), buf_len);
+
+    size_t pending_len = 0;
+    struct httpd_req_aux *ra = r->aux;
+
+    /* First fetch pending data from local buffer */
+    if (ra->sd->pending_len > 0) {
+        ESP_LOGD(TAG, LOG_FMT("pending length = %d"), ra->sd->pending_len);
+        pending_len = httpd_recv_pending(r, buf, buf_len);
+        buf     += pending_len;
+        buf_len -= pending_len;
+
+        /* If buffer filled then no need to recv.
+         * If asked to halt after receiving pending data then
+         * return with received length */
+        if (!buf_len || halt_after_pending) {
+            return pending_len;
+        }
+    }
+
+    /* Receive data of remaining length */
+    int ret = ra->sd->recv_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0);
+    if (ret < 0) {
+        ESP_LOGD(TAG, LOG_FMT("error in recv_fn"));
+        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);
+    return ret + pending_len;
+}
+
+int httpd_recv(httpd_req_t *r, char *buf, size_t buf_len)
+{
+    return httpd_recv_with_opt(r, buf, buf_len, false);
+}
+
+size_t httpd_unrecv(struct httpd_req *r, const char *buf, size_t buf_len)
+{
+    struct httpd_req_aux *ra = r->aux;
+    /* Truncate if external buf_len is greater than pending_data buffer size */
+    ra->sd->pending_len = MIN(sizeof(ra->sd->pending_data), buf_len);
+
+    /* Copy data into internal pending_data buffer */
+    size_t offset = sizeof(ra->sd->pending_data) - ra->sd->pending_len;
+    memcpy(ra->sd->pending_data + offset, buf, buf_len);
+    ESP_LOGD(TAG, LOG_FMT("length = %d"), ra->sd->pending_len);
+    return ra->sd->pending_len;
+}
+
+/**
+ * This API appends an additional header field-value pair in the HTTP response.
+ * But the header isn't sent out until any of the send APIs is executed.
+ */
+esp_err_t httpd_resp_set_hdr(httpd_req_t *r, const char *field, const char *value)
+{
+    if (r == NULL || field == NULL || value == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!httpd_valid_req(r)) {
+        return ESP_ERR_HTTPD_INVALID_REQ;
+    }
+
+    struct httpd_req_aux *ra = r->aux;
+    struct httpd_data *hd = (struct httpd_data *) r->handle;
+
+    /* Number of additional headers is limited */
+    if (ra->resp_hdrs_count >= hd->config.max_resp_headers) {
+        return ESP_ERR_HTTPD_RESP_HDR;
+    }
+
+    /* Assign header field-value pair */
+    ra->resp_hdrs[ra->resp_hdrs_count].field = field;
+    ra->resp_hdrs[ra->resp_hdrs_count].value = value;
+    ra->resp_hdrs_count++;
+
+    ESP_LOGD(TAG, LOG_FMT("new header = %s: %s"), field, value);
+    return ESP_OK;
+}
+
+/**
+ * This API sets the status of the HTTP response to the value specified.
+ * But the status isn't sent out until any of the send APIs is executed.
+ */
+esp_err_t httpd_resp_set_status(httpd_req_t *r, const char *status)
+{
+    if (r == NULL || status == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!httpd_valid_req(r)) {
+        return ESP_ERR_HTTPD_INVALID_REQ;
+    }
+
+    struct httpd_req_aux *ra = r->aux;
+    ra->status = (char *)status;
+    return ESP_OK;
+}
+
+/**
+ * This API sets the method/type of the HTTP response to the value specified.
+ * But the method isn't sent out until any of the send APIs is executed.
+ */
+esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type)
+{
+    if (r == NULL || type == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!httpd_valid_req(r)) {
+        return ESP_ERR_HTTPD_INVALID_REQ;
+    }
+
+    struct httpd_req_aux *ra = r->aux;
+    ra->content_type = (char *)type;
+    return ESP_OK;
+}
+
+esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len)
+{
+    if (r == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!httpd_valid_req(r)) {
+        return ESP_ERR_HTTPD_INVALID_REQ;
+    }
+
+    struct httpd_req_aux *ra = r->aux;
+    const char *httpd_hdr_str = "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n";
+    const char *colon_separator = ": ";
+    const char *cr_lf_seperator = "\r\n";
+
+    if (buf_len == -1) buf_len = strlen(buf);
+
+    /* Request headers are no longer available */
+    ra->req_hdrs_count = 0;
+
+    /* Size of essential headers is limited by scratch buffer size */
+    if (snprintf(ra->scratch, sizeof(ra->scratch), httpd_hdr_str,
+                 ra->status, ra->content_type, buf_len) >= sizeof(ra->scratch)) {
+        return ESP_ERR_HTTPD_RESP_HDR;
+    }
+
+    /* Sending essential headers */
+    if (httpd_send_all(r, ra->scratch, strlen(ra->scratch)) != ESP_OK) {
+        return ESP_ERR_HTTPD_RESP_SEND;
+    }
+
+    /* Sending additional headers based on set_header */
+    for (unsigned i = 0; i < ra->resp_hdrs_count; i++) {
+        /* Send header field */
+        if (httpd_send_all(r, ra->resp_hdrs[i].field, strlen(ra->resp_hdrs[i].field)) != ESP_OK) {
+            return ESP_ERR_HTTPD_RESP_SEND;
+        }
+        /* Send ': ' */
+        if (httpd_send_all(r, colon_separator, strlen(colon_separator)) != ESP_OK) {
+            return ESP_ERR_HTTPD_RESP_SEND;
+        }
+        /* Send header value */
+        if (httpd_send_all(r, ra->resp_hdrs[i].value, strlen(ra->resp_hdrs[i].value)) != ESP_OK) {
+            return ESP_ERR_HTTPD_RESP_SEND;
+        }
+        /* Send CR + LF */
+        if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
+            return ESP_ERR_HTTPD_RESP_SEND;
+        }
+    }
+
+    /* End header section */
+    if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
+        return ESP_ERR_HTTPD_RESP_SEND;
+    }
+
+    /* Sending content */
+    if (buf && buf_len) {
+        if (httpd_send_all(r, buf, buf_len) != ESP_OK) {
+            return ESP_ERR_HTTPD_RESP_SEND;
+        }
+    }
+    return ESP_OK;
+}
+
+esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len)
+{
+    if (r == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!httpd_valid_req(r)) {
+        return ESP_ERR_HTTPD_INVALID_REQ;
+    }
+
+    if (buf_len == -1) buf_len = strlen(buf);
+
+    struct httpd_req_aux *ra = r->aux;
+    const char *httpd_chunked_hdr_str = "HTTP/1.1 %s\r\nContent-Type: %s\r\nTransfer-Encoding: chunked\r\n";
+    const char *colon_separator = ": ";
+    const char *cr_lf_seperator = "\r\n";
+
+    /* Request headers are no longer available */
+    ra->req_hdrs_count = 0;
+
+    if (!ra->first_chunk_sent) {
+        /* Size of essential headers is limited by scratch buffer size */
+        if (snprintf(ra->scratch, sizeof(ra->scratch), httpd_chunked_hdr_str,
+                     ra->status, ra->content_type) >= sizeof(ra->scratch)) {
+            return ESP_ERR_HTTPD_RESP_HDR;
+        }
+
+        /* Sending essential headers */
+        if (httpd_send_all(r, ra->scratch, strlen(ra->scratch)) != ESP_OK) {
+            return ESP_ERR_HTTPD_RESP_SEND;
+        }
+
+        /* Sending additional headers based on set_header */
+        for (unsigned i = 0; i < ra->resp_hdrs_count; i++) {
+            /* Send header field */
+            if (httpd_send_all(r, ra->resp_hdrs[i].field, strlen(ra->resp_hdrs[i].field)) != ESP_OK) {
+                return ESP_ERR_HTTPD_RESP_SEND;
+            }
+            /* Send ': ' */
+            if (httpd_send_all(r, colon_separator, strlen(colon_separator)) != ESP_OK) {
+                return ESP_ERR_HTTPD_RESP_SEND;
+            }
+            /* Send header value */
+            if (httpd_send_all(r, ra->resp_hdrs[i].value, strlen(ra->resp_hdrs[i].value)) != ESP_OK) {
+                return ESP_ERR_HTTPD_RESP_SEND;
+            }
+            /* Send CR + LF */
+            if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
+                return ESP_ERR_HTTPD_RESP_SEND;
+            }
+        }
+
+        /* End header section */
+        if (httpd_send_all(r, cr_lf_seperator, strlen(cr_lf_seperator)) != ESP_OK) {
+            return ESP_ERR_HTTPD_RESP_SEND;
+        }
+        ra->first_chunk_sent = true;
+    }
+
+    /* Sending chunked content */
+    char len_str[10];
+    snprintf(len_str, sizeof(len_str), "%x\r\n", buf_len);
+    if (httpd_send_all(r, len_str, strlen(len_str)) != ESP_OK) {
+        return ESP_ERR_HTTPD_RESP_SEND;
+    }
+
+    if (buf) {
+        if (httpd_send_all(r, buf, (size_t) buf_len) != ESP_OK) {
+            return ESP_ERR_HTTPD_RESP_SEND;
+        }
+    }
+
+    /* Indicate end of chunk */
+    if (httpd_send_all(r, "\r\n", strlen("\r\n")) != ESP_OK) {
+        return ESP_ERR_HTTPD_RESP_SEND;
+    }
+    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)
+{
+    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";
+    }
+    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));
+}
+
+int httpd_req_recv(httpd_req_t *r, char *buf, size_t buf_len)
+{
+    if (r == NULL || buf == NULL) {
+        return HTTPD_SOCK_ERR_INVALID;
+    }
+
+    if (!httpd_valid_req(r)) {
+        ESP_LOGW(TAG, LOG_FMT("invalid request"));
+        return HTTPD_SOCK_ERR_INVALID;
+    }
+
+    struct httpd_req_aux *ra = r->aux;
+    ESP_LOGD(TAG, LOG_FMT("remaining length = %d"), ra->remaining_len);
+
+    if (buf_len > ra->remaining_len) {
+        buf_len = ra->remaining_len;
+    }
+    if (buf_len == 0) {
+        return buf_len;
+    }
+
+    int ret = httpd_recv(r, buf, buf_len);
+    if (ret < 0) {
+        ESP_LOGD(TAG, LOG_FMT("error in httpd_recv"));
+        return ret;
+    }
+    ra->remaining_len -= ret;
+    ESP_LOGD(TAG, LOG_FMT("received length = %d"), ret);
+    return ret;
+}
+
+int httpd_req_to_sockfd(httpd_req_t *r)
+{
+    if (r == NULL) {
+        return -1;
+    }
+
+    if (!httpd_valid_req(r)) {
+        ESP_LOGW(TAG, LOG_FMT("invalid request"));
+        return -1;
+    }
+
+    struct httpd_req_aux *ra = r->aux;
+    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(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags)
+{
+    (void)hd;
+    if (buf == NULL) {
+        return HTTPD_SOCK_ERR_INVALID;
+    }
+
+    int ret = send(sockfd, buf, buf_len, flags);
+    if (ret < 0) {
+        return httpd_sock_err("send", sockfd);
+    }
+    return ret;
+}
+
+int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, int flags)
+{
+    (void)hd;
+    if (buf == NULL) {
+        return HTTPD_SOCK_ERR_INVALID;
+    }
+
+    int ret = recv(sockfd, buf, buf_len, flags);
+    if (ret < 0) {
+        return httpd_sock_err("recv", sockfd);
+    }
+    return ret;
+}
diff --git a/components/esp_http_server/src/httpd_uri.c b/components/esp_http_server/src/httpd_uri.c
new file mode 100644
index 00000000..e31a6108
--- /dev/null
+++ b/components/esp_http_server/src/httpd_uri.c
@@ -0,0 +1,224 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+#include <errno.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <http_parser.h>
+
+#include <esp_http_server.h>
+#include "esp_httpd_priv.h"
+
+static const char *TAG = "httpd_uri";
+
+static int httpd_find_uri_handler(struct httpd_data *hd,
+                                  const char* uri,
+                                  httpd_method_t method)
+{
+    for (int i = 0; i < hd->config.max_uri_handlers; i++) {
+        if (hd->hd_calls[i]) {
+            ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri);
+            if ((hd->hd_calls[i]->method == method) &&          // First match methods
+                (strcmp(hd->hd_calls[i]->uri, uri) == 0)) {     // Then match uri strings
+                return i;
+            }
+        }
+    }
+    return -1;
+}
+
+esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
+                                     const httpd_uri_t *uri_handler)
+{
+    if (handle == NULL || uri_handler == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    struct httpd_data *hd = (struct httpd_data *) handle;
+
+    /* Make sure another handler with same URI and method
+     * is not already registered
+     */
+    if (httpd_find_uri_handler(handle, uri_handler->uri,
+                               uri_handler->method) != -1) {
+        ESP_LOGW(TAG, LOG_FMT("handler %s with method %d already registered"),
+                 uri_handler->uri, uri_handler->method);
+        return ESP_ERR_HTTPD_HANDLER_EXISTS;
+    }
+
+    for (int i = 0; i < hd->config.max_uri_handlers; i++) {
+        if (hd->hd_calls[i] == NULL) {
+            hd->hd_calls[i] = malloc(sizeof(httpd_uri_t));
+            if (hd->hd_calls[i] == NULL) {
+                /* Failed to allocate memory */
+                return ESP_ERR_HTTPD_ALLOC_MEM;
+            }
+
+            /* Copy URI string */
+            hd->hd_calls[i]->uri = strdup(uri_handler->uri);
+            if (hd->hd_calls[i]->uri == NULL) {
+                /* Failed to allocate memory */
+                free(hd->hd_calls[i]);
+                return ESP_ERR_HTTPD_ALLOC_MEM;
+            }
+
+            /* Copy remaining members */
+            hd->hd_calls[i]->method   = uri_handler->method;
+            hd->hd_calls[i]->handler  = uri_handler->handler;
+            hd->hd_calls[i]->user_ctx = uri_handler->user_ctx;
+            ESP_LOGD(TAG, LOG_FMT("[%d] installed %s"), i, uri_handler->uri);
+            return ESP_OK;
+        }
+        ESP_LOGD(TAG, LOG_FMT("[%d] exists %s"), i, hd->hd_calls[i]->uri);
+    }
+    ESP_LOGW(TAG, LOG_FMT("no slots left for registering handler"));
+    return ESP_ERR_HTTPD_HANDLERS_FULL;
+}
+
+esp_err_t httpd_unregister_uri_handler(httpd_handle_t handle,
+                                       const char *uri, httpd_method_t method)
+{
+    if (handle == NULL || uri == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    struct httpd_data *hd = (struct httpd_data *) handle;
+    int i = httpd_find_uri_handler(hd, uri, method);
+
+    if (i != -1) {
+        ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
+
+        free((char*)hd->hd_calls[i]->uri);
+        free(hd->hd_calls[i]);
+        hd->hd_calls[i] = NULL;
+        return ESP_OK;
+    }
+    ESP_LOGW(TAG, LOG_FMT("handler %s with method %d not found"), uri, method);
+    return ESP_ERR_NOT_FOUND;
+}
+
+esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char *uri)
+{
+    if (handle == NULL || uri == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    struct httpd_data *hd = (struct httpd_data *) handle;
+    bool found = false;
+
+    for (int i = 0; i < hd->config.max_uri_handlers; i++) {
+        if ((hd->hd_calls[i] != NULL) &&
+            (strcmp(hd->hd_calls[i]->uri, uri) == 0)) {
+            ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, uri);
+
+            free((char*)hd->hd_calls[i]->uri);
+            free(hd->hd_calls[i]);
+            hd->hd_calls[i] = NULL;
+            found = true;
+        }
+    }
+    if (!found) {
+        ESP_LOGW(TAG, LOG_FMT("no handler found for URI %s"), uri);
+    }
+    return (found ? ESP_OK : ESP_ERR_NOT_FOUND);
+}
+
+void httpd_unregister_all_uri_handlers(struct httpd_data *hd)
+{
+    for (unsigned i = 0; i < hd->config.max_uri_handlers; i++) {
+        if (hd->hd_calls[i]) {
+            ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
+
+            free((char*)hd->hd_calls[i]->uri);
+            free(hd->hd_calls[i]);
+        }
+    }
+}
+
+/* Alternate implmentation of httpd_find_uri_handler()
+ * which takes a uri_len field. This is useful when the URI
+ * string contains extra parameters that are not to be included
+ * while matching with the registered URI_handler strings
+ */
+static httpd_uri_t* httpd_find_uri_handler2(httpd_err_resp_t *err,
+                                            struct httpd_data *hd,
+                                            const char *uri, size_t uri_len,
+                                            httpd_method_t method)
+{
+    *err = 0;
+    for (int i = 0; i < hd->config.max_uri_handlers; i++) {
+        if (hd->hd_calls[i]) {
+            ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri);
+            if ((strlen(hd->hd_calls[i]->uri) == uri_len) &&            // First match uri length
+                (strncmp(hd->hd_calls[i]->uri, uri, uri_len) == 0))  {  // Then match uri strings
+                if (hd->hd_calls[i]->method == method)  {               // Finally match methods
+                    return hd->hd_calls[i];
+                }
+                /* URI found but method not allowed.
+                 * If URI IS found later then this
+                 * error is to be neglected */
+                *err = HTTPD_405_METHOD_NOT_ALLOWED;
+            }
+        }
+    }
+    if (*err == 0) {
+        *err = HTTPD_404_NOT_FOUND;
+    }
+    return NULL;
+}
+
+esp_err_t httpd_uri(struct httpd_data *hd)
+{
+    httpd_uri_t            *uri = NULL;
+    httpd_req_t            *req = &hd->hd_req;
+    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;
+
+    ESP_LOGD(TAG, LOG_FMT("request for %s with type %d"), req->uri, req->method);
+    /* URL parser result contains offset and length of path string */
+    if (res->field_set & (1 << UF_PATH)) {
+        uri = httpd_find_uri_handler2(&err, hd,
+                                      req->uri + res->field_data[UF_PATH].off,
+                                      res->field_data[UF_PATH].len,
+                                      req->method);
+    }
+
+    /* If URI with method not found, respond with error code */
+    if (uri == NULL) {
+        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);
+            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);
+            default:
+                return ESP_FAIL;
+        }
+    }
+
+    /* Attach user context data (passed during URI registration) into request */
+    req->user_ctx = uri->user_ctx;
+
+    /* Invoke handler */
+    if (uri->handler(req) != ESP_OK) {
+        /* Handler returns error, this socket should be closed */
+        ESP_LOGW(TAG, LOG_FMT("uri handler execution failed"));
+        return ESP_FAIL;
+    }
+    return ESP_OK;
+}
diff --git a/components/esp_http_server/src/port/esp32/osal.h b/components/esp_http_server/src/port/esp32/osal.h
new file mode 100644
index 00000000..d4e76fcd
--- /dev/null
+++ b/components/esp_http_server/src/port/esp32/osal.h
@@ -0,0 +1,69 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _OSAL_H_
+#define _OSAL_H_
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <esp_timer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define OS_SUCCESS ESP_OK
+#define OS_FAIL    ESP_FAIL
+
+typedef TaskHandle_t othread_t;
+
+static inline int httpd_os_thread_create(othread_t *thread,
+                                 const char *name, uint16_t stacksize, int prio,
+                                 void (*thread_routine)(void *arg), void *arg)
+{
+    int ret = xTaskCreate(thread_routine, name, stacksize, arg, prio, thread);
+    if (ret == pdPASS) {
+        return OS_SUCCESS;
+    }
+    return OS_FAIL;
+}
+
+/* Only self delete is supported */
+static inline void httpd_os_thread_delete()
+{
+    vTaskDelete(xTaskGetCurrentTaskHandle());
+}
+
+static inline void httpd_os_thread_sleep(int msecs)
+{
+    vTaskDelay(msecs / portTICK_RATE_MS);
+}
+
+static inline int64_t httpd_os_get_timestamp()
+{
+    return esp_timer_get_time();
+}
+
+static inline othread_t httpd_os_thread_handle()
+{
+    return xTaskGetCurrentTaskHandle();
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ! _OSAL_H_ */
diff --git a/components/esp_http_server/src/util/ctrl_sock.c b/components/esp_http_server/src/util/ctrl_sock.c
new file mode 100644
index 00000000..5c84f80e
--- /dev/null
+++ b/components/esp_http_server/src/util/ctrl_sock.c
@@ -0,0 +1,77 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "ctrl_sock.h"
+
+/* Control socket, because in some network stacks select can't be woken up any
+ * other way
+ */
+int cs_create_ctrl_sock(int port)
+{
+    int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (fd < 0) {
+        return -1;
+    }
+
+    int ret;
+    struct sockaddr_in addr;
+    memset(&addr, 0, sizeof(addr));
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(port);
+    inet_aton("127.0.0.1", &addr.sin_addr);
+    ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+    if (ret < 0) {
+        close(fd);
+        return -1;
+    }
+    return fd;
+}
+
+void cs_free_ctrl_sock(int fd)
+{
+    close(fd);
+}
+
+int cs_send_to_ctrl_sock(int send_fd, int port, void *data, unsigned int data_len)
+{
+    int ret;
+    struct sockaddr_in to_addr;
+    to_addr.sin_family = AF_INET;
+    to_addr.sin_port = htons(port);
+    inet_aton("127.0.0.1", &to_addr.sin_addr);
+    ret = sendto(send_fd, data, data_len, 0, (struct sockaddr *)&to_addr, sizeof(to_addr));
+
+    if (ret < 0) {
+        return -1;
+    }
+    return ret;
+}
+
+int cs_recv_from_ctrl_sock(int fd, void *data, unsigned int data_len)
+{
+    int ret;
+    ret = recvfrom(fd, data, data_len, 0, NULL, NULL);
+
+    if (ret < 0) {
+        return -1;
+    }
+    return ret;
+}
diff --git a/components/esp_http_server/src/util/ctrl_sock.h b/components/esp_http_server/src/util/ctrl_sock.h
new file mode 100644
index 00000000..d392d353
--- /dev/null
+++ b/components/esp_http_server/src/util/ctrl_sock.h
@@ -0,0 +1,99 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * \file ctrl_sock.h
+ * \brief Control Socket for select() wakeup
+ *
+ * LWIP doesn't allow an easy mechanism to on-demand wakeup a thread
+ * sleeping on select. This is a common requirement for sending
+ * control commands to a network server. This control socket API
+ * facilitates the same.
+ */
+#ifndef _CTRL_SOCK_H_
+#define _CTRL_SOCK_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Create a control socket
+ *
+ *      LWIP doesn't allow an easy mechanism to on-demand wakeup a thread
+ *      sleeping on select. This is a common requirement for sending
+ *      control commands to a network server. This control socket API
+ *      facilitates the same.
+ *
+ *      This API will create a UDP control socket on the specified port. It
+ *      will return a socket descriptor that can then be added to your
+ *      fd_set in select()
+ *
+ * @param[in] port the local port on which the control socket will listen
+ *
+ * @return - the socket descriptor that can be added to the fd_set in select.
+ *         - an error code if less than zero
+ */
+int cs_create_ctrl_sock(int port);
+
+/**
+ * @brief Free the control socket
+ *
+ *      This frees up the control socket that was earlier created using
+ *      cs_create_ctrl_sock()
+ *
+ * @param[in] fd the socket descriptor associated with this control socket
+ */
+void cs_free_ctrl_sock(int fd);
+
+/**
+ * @brief Send data to control socket
+ *
+ *      This API sends data to the control socket. If a server is blocked
+ *      on select() with the control socket, this call will wake up that
+ *      server.
+ *
+ * @param[in] send_fd the socket for sending ctrl messages
+ * @param[in] port the port on which the control socket was created
+ * @param[in] data pointer to a buffer that contains data to send on the socket
+ * @param[in] data_len the length of the data contained in the buffer pointed to be data
+ *
+ * @return  - the number of bytes sent to the control socket
+ *          - an error code if less than zero
+ */
+int cs_send_to_ctrl_sock(int send_fd, int port, void *data, unsigned int data_len);
+
+/**
+ * @brief Receive data from control socket
+ *
+ *      This API receives any data that was sent to the control
+ *      socket. This will be typically called from the server thread to
+ *      process any commands on this socket.
+ *
+ * @param[in] fd the socket descriptor of the control socket
+ * @param[in] data pointer to a buffer that will be used to store
+ * received from the control socket
+ * @param[in] data_len the maximum length of the data that can be
+ * stored in the buffer pointed by data
+ *
+ * @return  - the number of bytes received from the control socket
+ *          - an error code if less than zero
+ */
+int cs_recv_from_ctrl_sock(int fd, void *data, unsigned int data_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ! _CTRL_SOCK_H_ */
diff --git a/components/esp_http_server/test/CMakeLists.txt b/components/esp_http_server/test/CMakeLists.txt
new file mode 100644
index 00000000..7587213b
--- /dev/null
+++ b/components/esp_http_server/test/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(COMPONENT_SRCDIRS ".")
+set(COMPONENT_ADD_INCLUDEDIRS ".")
+
+set(COMPONENT_REQUIRES unity esp_http_server)
+
+register_component()
diff --git a/components/esp_http_server/test/component.mk b/components/esp_http_server/test/component.mk
new file mode 100644
index 00000000..ce464a21
--- /dev/null
+++ b/components/esp_http_server/test/component.mk
@@ -0,0 +1 @@
+COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
diff --git a/components/esp_http_server/test/test_http_server.c b/components/esp_http_server/test/test_http_server.c
new file mode 100644
index 00000000..e343c99c
--- /dev/null
+++ b/components/esp_http_server/test/test_http_server.c
@@ -0,0 +1,169 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <esp_system.h>
+#include <esp_http_server.h>
+
+#include "unity.h"
+#include "test_utils.h"
+
+int pre_start_mem, post_stop_mem, post_stop_min_mem;
+bool basic_sanity = true;
+
+esp_err_t null_func(httpd_req_t *req)
+{
+    return ESP_OK;
+}
+
+httpd_uri_t handler_limit_uri (char* path)
+{
+    httpd_uri_t uri = {
+        .uri      = path,
+        .method   = HTTP_GET,
+        .handler  = null_func,
+        .user_ctx = NULL,
+    };
+    return uri;
+};
+
+static inline unsigned num_digits(unsigned x)
+{
+    unsigned digits = 1;
+    while ((x = x/10) != 0) {
+        digits++;
+    }
+    return digits;
+}
+
+#define HTTPD_TEST_MAX_URI_HANDLERS 8
+
+void test_handler_limit(httpd_handle_t hd)
+{
+    int i;
+    char x[HTTPD_TEST_MAX_URI_HANDLERS+1][num_digits(HTTPD_TEST_MAX_URI_HANDLERS)+1];
+    httpd_uri_t uris[HTTPD_TEST_MAX_URI_HANDLERS+1];
+
+    for (i = 0; i < HTTPD_TEST_MAX_URI_HANDLERS + 1; i++) {
+        sprintf(x[i],"%d",i);
+        uris[i] = handler_limit_uri(x[i]);
+    }
+
+    /* Register multiple instances of the same handler for MAX URI Handlers */
+    for (i = 0; i < HTTPD_TEST_MAX_URI_HANDLERS; i++) {
+        TEST_ASSERT(httpd_register_uri_handler(hd, &uris[i]) == ESP_OK);
+    }
+
+    /* Register the MAX URI + 1 Handlers should fail */
+    TEST_ASSERT(httpd_register_uri_handler(hd, &uris[HTTPD_TEST_MAX_URI_HANDLERS]) != ESP_OK);
+
+    /* Unregister the one of the Handler should pass */
+    TEST_ASSERT(httpd_unregister_uri_handler(hd, uris[0].uri, uris[0].method) == ESP_OK);
+
+    /* Unregister non added Handler should fail */
+    TEST_ASSERT(httpd_unregister_uri_handler(hd, uris[0].uri, uris[0].method) != ESP_OK);
+
+    /* Register the MAX URI Handler should pass */
+    TEST_ASSERT(httpd_register_uri_handler(hd, &uris[0]) == ESP_OK);
+
+    /* Reregister same instance of handler should fail */
+    TEST_ASSERT(httpd_register_uri_handler(hd, &uris[0]) != ESP_OK);
+
+    /* Register the MAX URI + 1 Handlers should fail */
+    TEST_ASSERT(httpd_register_uri_handler(hd, &uris[HTTPD_TEST_MAX_URI_HANDLERS]) != ESP_OK);
+
+    /* Unregister the same handler for MAX URI Handlers */
+    for (i = 0; i < HTTPD_TEST_MAX_URI_HANDLERS; i++) {
+        TEST_ASSERT(httpd_unregister_uri_handler(hd, uris[i].uri, uris[i].method) == ESP_OK);
+    }
+    basic_sanity = false;
+}
+
+/********************* Test Handler Limit End *******************/
+
+httpd_handle_t test_httpd_start(uint16_t id)
+{
+    httpd_handle_t hd;
+    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+    config.max_uri_handlers = HTTPD_TEST_MAX_URI_HANDLERS;
+    config.server_port += id;
+    config.ctrl_port += id;
+    TEST_ASSERT(httpd_start(&hd, &config) == ESP_OK)
+    return hd;
+}
+
+#define SERVER_INSTANCES 2
+
+/* Currently this only tests for the number of tasks.
+ * Heap leakage is not tested as LWIP allocates memory
+ * which may not be freed immedietly causing erroneous
+ * evaluation. Another test to implement would be the
+ * monitoring of open sockets, but LWIP presently provides
+ * no such API for getting the number of open sockets.
+ */
+TEST_CASE("Leak Test", "[HTTP SERVER]")
+{
+    httpd_handle_t hd[SERVER_INSTANCES];
+    unsigned task_count;
+    bool res = true;
+
+    test_case_uses_tcpip();
+
+    task_count = uxTaskGetNumberOfTasks();
+    printf("Initial task count: %d\n", task_count);
+
+    pre_start_mem = esp_get_free_heap_size();
+
+    for (int i = 0; i < SERVER_INSTANCES; i++) {
+        hd[i] = test_httpd_start(i);
+        vTaskDelay(10);
+        unsigned num_tasks = uxTaskGetNumberOfTasks();
+        task_count++;
+        if (num_tasks != task_count) {
+            printf("Incorrect task count (starting): %d expected %d\n",
+                   num_tasks, task_count);
+            res = false;
+        }
+    }
+
+    for (int i = 0; i < SERVER_INSTANCES; i++) {
+        if (httpd_stop(hd[i]) != ESP_OK) {
+            printf("Failed to stop httpd task %d\n", i);
+            res = false;
+        }
+        vTaskDelay(10);
+        unsigned num_tasks = uxTaskGetNumberOfTasks();
+        task_count--;
+        if (num_tasks != task_count) {
+            printf("Incorrect task count (stopping): %d expected %d\n",
+                   num_tasks, task_count);
+            res = false;
+        }
+    }
+    post_stop_mem = esp_get_free_heap_size();
+    TEST_ASSERT(res == true);
+}
+
+TEST_CASE("Basic Functionality Tests", "[HTTP SERVER]")
+{
+    httpd_handle_t hd;
+    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+
+    test_case_uses_tcpip();
+
+    TEST_ASSERT(httpd_start(&hd, &config) == ESP_OK);
+    test_handler_limit(hd);
+    TEST_ASSERT(httpd_stop(hd) == ESP_OK);
+}
diff --git a/examples/protocols/http_server/advanced_tests/CMakeLists.txt b/examples/protocols/http_server/advanced_tests/CMakeLists.txt
new file mode 100644
index 00000000..b6f65f8f
--- /dev/null
+++ b/examples/protocols/http_server/advanced_tests/CMakeLists.txt
@@ -0,0 +1,8 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(tests)
+
+target_include_directories(tests.elf PRIVATE main/include)
diff --git a/examples/protocols/http_server/advanced_tests/Makefile b/examples/protocols/http_server/advanced_tests/Makefile
new file mode 100644
index 00000000..178ddf69
--- /dev/null
+++ b/examples/protocols/http_server/advanced_tests/Makefile
@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := tests
+
+include $(IDF_PATH)/make/project.mk
+
diff --git a/examples/protocols/http_server/advanced_tests/http_server_advanced_test.py b/examples/protocols/http_server/advanced_tests/http_server_advanced_test.py
new file mode 100644
index 00000000..0294bed9
--- /dev/null
+++ b/examples/protocols/http_server/advanced_tests/http_server_advanced_test.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+import imp
+import re
+import os
+import sys
+import string
+import random
+import socket
+
+# This environment variable is expected on the host machine
+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)
+
+# When running on local machine execute the following before running this script
+# > make app bootloader
+# > make print_flash_cmd | tail -n 1 > build/download.config
+# > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
+
+import TinyFW
+import IDF
+import Utility
+
+# Import client module
+expath = os.path.dirname(os.path.realpath(__file__))
+client = imp.load_source("client", expath + "/scripts/test.py")
+
+# Due to connectivity issues (between runner host and DUT) in the runner environment,
+# some of the `advanced_tests` are ignored. These tests are intended for verifying
+# the expected limits of the http_server capabilities, and implement sending and receiving
+# of large HTTP packets and malformed requests, running multiple parallel sessions, etc.
+# It is advised that all these tests be run locally, when making changes or adding new
+# features to this component.
+@IDF.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_http_server_advanced(env, extra_data):
+    # Acquire DUT
+    dut1 = env.get_dut("http_server", "examples/protocols/http_server/advanced_tests")
+
+    # Get binary file
+    binary_file = os.path.join(dut1.app.binary_path, "tests.bin")
+    bin_size = os.path.getsize(binary_file)
+    IDF.log_performance("http_server_bin_size", "{}KB".format(bin_size//1024))
+    IDF.check_performance("http_server_bin_size", bin_size//1024)
+
+    # Upload binary and start testing
+    Utility.console_log("Starting http_server advanced test app")
+    dut1.start_app()
+
+    # Parse IP address of STA
+    Utility.console_log("Waiting to connect with AP")
+    got_ip = dut1.expect(re.compile(r"(?:[\s\S]*)Got IP: '(\d+.\d+.\d+.\d+)'"), timeout=30)[0]
+
+    got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Started HTTP server on port: '(\d+)'"), timeout=15)[0]
+    result = dut1.expect(re.compile(r"(?:[\s\S]*)Max URI handlers: '(\d+)'(?:[\s\S]*)Max Open Sessions: '(\d+)'(?:[\s\S]*)Max Header Length: '(\d+)'(?:[\s\S]*)Max URI Length: '(\d+)'(?:[\s\S]*)Max Stack Size: '(\d+)'"), timeout=15)
+    max_uri_handlers = int(result[0])
+    max_sessions = int(result[1])
+    max_hdr_len = int(result[2])
+    max_uri_len = int(result[3])
+    max_stack_size = int(result[4])
+
+    Utility.console_log("Got IP   : " + got_ip)
+    Utility.console_log("Got Port : " + got_port)
+
+    # Run test script
+    # If failed raise appropriate exception
+    failed = False
+
+    Utility.console_log("Sessions and Context Tests...")
+    if not client.spillover_session(got_ip, got_port, max_sessions):
+        Utility.console_log("Ignoring failure")
+    if not client.parallel_sessions_adder(got_ip, got_port, max_sessions):
+        Utility.console_log("Ignoring failure")
+    if not client.leftover_data_test(got_ip, got_port):
+        failed = True
+    if not client.async_response_test(got_ip, got_port):
+        failed = True
+    if not client.recv_timeout_test(got_ip, got_port):
+        failed = True
+
+    ## This test fails a lot! Enable when connection is stable
+    #test_size = 50*1024 # 50KB
+    #if not client.packet_size_limit_test(got_ip, got_port, test_size):
+    #    Utility.console_log("Ignoring failure")
+
+    Utility.console_log("Getting initial stack usage...")
+    if not client.get_hello(got_ip, got_port):
+        failed = True
+
+    inital_stack = int(dut1.expect(re.compile(r"(?:[\s\S]*)Free Stack for server task: '(\d+)'"), timeout=15)[0])
+
+    if inital_stack < 0.1*max_stack_size:
+        Utility.console_log("More than 90% of stack being used on server start")
+        failed = True
+
+    Utility.console_log("Basic HTTP Client Tests...")
+    if not client.get_hello(got_ip, got_port):
+        failed = True
+    if not client.post_hello(got_ip, got_port):
+        failed = True
+    if not client.put_hello(got_ip, got_port):
+        failed = True
+    if not client.post_echo(got_ip, got_port):
+        failed = True
+    if not client.get_echo(got_ip, got_port):
+        failed = True
+    if not client.put_echo(got_ip, got_port):
+        failed = True
+    if not client.get_hello_type(got_ip, got_port):
+        failed = True
+    if not client.get_hello_status(got_ip, got_port):
+        failed = True
+    if not client.get_false_uri(got_ip, got_port):
+        failed = True
+
+    Utility.console_log("Error code tests...")
+    if not client.code_500_server_error_test(got_ip, got_port):
+        failed = True
+    if not client.code_501_method_not_impl(got_ip, got_port):
+        failed = True
+    if not client.code_505_version_not_supported(got_ip, got_port):
+        failed = True
+    if not client.code_400_bad_request(got_ip, got_port):
+        failed = True
+    if not client.code_404_not_found(got_ip, got_port):
+        failed = True
+    if not client.code_405_method_not_allowed(got_ip, got_port):
+        failed = True
+    if not client.code_408_req_timeout(got_ip, got_port):
+        failed = True
+    if not client.code_414_uri_too_long(got_ip, got_port, max_uri_len):
+        Utility.console_log("Ignoring failure")
+    if not client.code_431_hdr_too_long(got_ip, got_port, max_hdr_len):
+        Utility.console_log("Ignoring failure")
+    if not client.test_upgrade_not_supported(got_ip, got_port):
+        failed = True
+
+    Utility.console_log("Getting final stack usage...")
+    if not client.get_hello(got_ip, got_port):
+        failed = True
+
+    final_stack = int(dut1.expect(re.compile(r"(?:[\s\S]*)Free Stack for server task: '(\d+)'"), timeout=15)[0])
+
+    if final_stack < 0.05*max_stack_size:
+        Utility.console_log("More than 95% of stack got used during tests")
+        failed = True
+
+    if failed:
+        raise RuntimeError
+
+if __name__ == '__main__':
+    test_examples_protocol_http_server_advanced()
diff --git a/examples/protocols/http_server/advanced_tests/main/CMakeLists.txt b/examples/protocols/http_server/advanced_tests/main/CMakeLists.txt
new file mode 100644
index 00000000..9fd69b1d
--- /dev/null
+++ b/examples/protocols/http_server/advanced_tests/main/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(COMPONENT_SRCS "main.c"
+                   "tests.c")
+set(COMPONENT_ADD_INCLUDEDIRS ". include")
+
+register_component()
diff --git a/examples/protocols/http_server/advanced_tests/main/Kconfig.projbuild b/examples/protocols/http_server/advanced_tests/main/Kconfig.projbuild
new file mode 100644
index 00000000..9e2813c6
--- /dev/null
+++ b/examples/protocols/http_server/advanced_tests/main/Kconfig.projbuild
@@ -0,0 +1,16 @@
+menu "Example Configuration"
+
+config WIFI_SSID
+    string "WiFi SSID"
+    default "myssid"
+    help
+        SSID (network name) for the example to connect to.
+
+config WIFI_PASSWORD
+    string "WiFi Password"
+    default "mypassword"
+    help
+        WiFi password (WPA or WPA2) for the example to use.
+        Can be left blank if the network has no security set.
+
+endmenu
diff --git a/examples/protocols/http_server/advanced_tests/main/component.mk b/examples/protocols/http_server/advanced_tests/main/component.mk
new file mode 100644
index 00000000..0b9d7585
--- /dev/null
+++ b/examples/protocols/http_server/advanced_tests/main/component.mk
@@ -0,0 +1,5 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
diff --git a/examples/protocols/http_server/advanced_tests/main/include/tests.h b/examples/protocols/http_server/advanced_tests/main/include/tests.h
new file mode 100644
index 00000000..8f5db8ea
--- /dev/null
+++ b/examples/protocols/http_server/advanced_tests/main/include/tests.h
@@ -0,0 +1,9 @@
+#ifndef __HTTPD_TESTS_H__
+#define __HTTPD_TESTS_H__
+
+#include <esp_http_server.h>
+
+extern httpd_handle_t   start_tests(void);
+extern void              stop_tests(httpd_handle_t hd);
+
+#endif // __HTTPD_TESTS_H__
diff --git a/examples/protocols/http_server/advanced_tests/main/main.c b/examples/protocols/http_server/advanced_tests/main/main.c
new file mode 100644
index 00000000..de32966c
--- /dev/null
+++ b/examples/protocols/http_server/advanced_tests/main/main.c
@@ -0,0 +1,81 @@
+#include "esp_wifi.h"
+#include "esp_event_loop.h"
+#include "esp_log.h"
+#include "esp_system.h"
+#include "nvs_flash.h"
+
+#include "tests.h"
+
+/* The examples use simple WiFi configuration that you can set via
+   'make menuconfig'.
+
+   If you'd rather not, just change the below entries to strings with
+   the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
+*/
+#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
+#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
+
+static const char *TAG="TEST_WIFI";
+
+static esp_err_t event_handler(void *ctx, system_event_t *event)
+{
+    httpd_handle_t *hd = (httpd_handle_t *) ctx;
+
+    switch(event->event_id) {
+    case SYSTEM_EVENT_STA_START:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
+        ESP_ERROR_CHECK(esp_wifi_connect());
+        break;
+    case SYSTEM_EVENT_STA_GOT_IP:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
+        ESP_LOGI(TAG, "Got IP: '%s'",
+                 ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
+
+        // Start webserver tests
+        if (*hd == NULL) {
+            *hd = start_tests();
+        }
+
+        break;
+    case SYSTEM_EVENT_STA_DISCONNECTED:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
+        ESP_ERROR_CHECK(esp_wifi_connect());
+
+        // Stop webserver tests
+        if (*hd) {
+            stop_tests(*hd);
+            *hd = NULL;
+        }
+
+        break;
+    default:
+        break;
+    }
+    return ESP_OK;
+}
+
+static void initialise_wifi(void)
+{
+    tcpip_adapter_init();
+    static httpd_handle_t hd = NULL;
+    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, &hd));
+    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
+    wifi_config_t wifi_config = {
+        .sta = {
+            .ssid = EXAMPLE_WIFI_SSID,
+            .password = EXAMPLE_WIFI_PASS,
+        },
+    };
+    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
+    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
+    ESP_ERROR_CHECK(esp_wifi_start());
+}
+
+void app_main()
+{
+    ESP_ERROR_CHECK(nvs_flash_init());
+    initialise_wifi();
+}
diff --git a/examples/protocols/http_server/advanced_tests/main/tests.c b/examples/protocols/http_server/advanced_tests/main/tests.c
new file mode 100644
index 00000000..8845585b
--- /dev/null
+++ b/examples/protocols/http_server/advanced_tests/main/tests.c
@@ -0,0 +1,310 @@
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <esp_log.h>
+#include <esp_system.h>
+#include <esp_http_server.h>
+
+#include "tests.h"
+
+static const char *TAG="TESTS";
+
+int pre_start_mem, post_stop_mem, post_stop_min_mem;
+bool basic_sanity = true;
+
+struct async_resp_arg {
+    httpd_handle_t hd;
+    int fd;
+};
+
+/********************* Basic Handlers Start *******************/
+
+esp_err_t hello_get_handler(httpd_req_t *req)
+{
+#define STR "Hello World!"
+    ESP_LOGI(TAG, "Free Stack for server task: '%d'", uxTaskGetStackHighWaterMark(NULL));
+    httpd_resp_send(req, STR, strlen(STR));
+    return ESP_OK;
+#undef STR
+}
+
+esp_err_t hello_type_get_handler(httpd_req_t *req)
+{
+#define STR "Hello World!"
+    httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
+    httpd_resp_send(req, STR, strlen(STR));
+    return ESP_OK;
+#undef STR
+}
+
+esp_err_t hello_status_get_handler(httpd_req_t *req)
+{
+#define STR "Hello World!"
+    httpd_resp_set_status(req, HTTPD_500);
+    httpd_resp_send(req, STR, strlen(STR));
+    return ESP_OK;
+#undef STR
+}
+
+esp_err_t echo_post_handler(httpd_req_t *req)
+{
+    ESP_LOGI(TAG, "/echo handler read content length %d", req->content_len);
+
+    char*  buf = malloc(req->content_len + 1);
+    size_t off = 0;
+    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 == HTTPD_SOCK_ERR_TIMEOUT) {
+                httpd_resp_send_408(req);
+            }
+            free (buf);
+            return ESP_FAIL;
+        }
+        off += ret;
+        ESP_LOGI(TAG, "/echo handler recv length %d", ret);
+    }
+    buf[off] = '\0';
+
+    if (req->content_len < 128) {
+        ESP_LOGI(TAG, "/echo handler read %s", buf);
+    }
+
+    /* Search for Custom header field */
+    char*  req_hdr = 0;
+    size_t hdr_len = httpd_req_get_hdr_value_len(req, "Custom");
+    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);
+        }
+    }
+    httpd_resp_send(req, buf, req->content_len);
+    free (req_hdr);
+    free (buf);
+    return ESP_OK;
+}
+
+void adder_free_func(void *ctx)
+{
+    ESP_LOGI(TAG, "Custom Free Context function called");
+    free(ctx);
+}
+
+/* Create a context, keep incrementing value in the context, by whatever was
+ * received. Return the result
+ */
+esp_err_t adder_post_handler(httpd_req_t *req)
+{
+    char buf[10];
+    char outbuf[50];
+    int  ret;
+
+    /* Read data received in the request */
+    ret = httpd_req_recv(req, buf, sizeof(buf));
+    if (ret <= 0) {
+        if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
+            httpd_resp_send_408(req);
+        }
+        return ESP_FAIL;
+    }
+
+    buf[ret] = '\0';
+    int val = atoi(buf);
+    ESP_LOGI(TAG, "/adder handler read %d", val);
+
+    if (! req->sess_ctx) {
+        ESP_LOGI(TAG, "/adder allocating new session");
+        req->sess_ctx = malloc(sizeof(int));
+        req->free_ctx = adder_free_func;
+        *(int *)req->sess_ctx = 0;
+    }
+    int *adder = (int *)req->sess_ctx;
+    *adder += val;
+
+    snprintf(outbuf, sizeof(outbuf),"%d", *adder);
+    httpd_resp_send(req, outbuf, strlen(outbuf));
+    return ESP_OK;
+}
+
+esp_err_t leftover_data_post_handler(httpd_req_t *req)
+{
+    /* Only echo the first 10 bytes of the request, leaving the rest of the
+     * request data as is.
+     */
+    char buf[11];
+    int  ret;
+
+    /* Read data received in the request */
+    ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
+    if (ret <= 0) {
+        if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
+            httpd_resp_send_408(req);
+        }
+        return ESP_FAIL;
+    }
+
+    buf[ret] = '\0';
+    ESP_LOGI(TAG, "leftover data handler read %s", buf);
+    httpd_resp_send(req, buf, strlen(buf));
+    return ESP_OK;
+}
+
+int httpd_default_send(httpd_handle_t hd, int sockfd, const char *buf, unsigned buf_len, int flags);
+void generate_async_resp(void *arg)
+{
+    char buf[250];
+    struct async_resp_arg *resp_arg = (struct async_resp_arg *)arg;
+    httpd_handle_t hd = resp_arg->hd;
+    int fd = resp_arg->fd;
+#define HTTPD_HDR_STR      "HTTP/1.1 200 OK\r\n"                   \
+                           "Content-Type: text/html\r\n"           \
+                           "Content-Length: %d\r\n"
+#define STR "Hello Double World!"
+
+    ESP_LOGI(TAG, "Executing queued work fd : %d", fd);
+
+    snprintf(buf, sizeof(buf), HTTPD_HDR_STR,
+         strlen(STR));
+    httpd_default_send(hd, fd, buf, strlen(buf), 0);
+    /* Space for sending additional headers based on set_header */
+    httpd_default_send(hd, fd, "\r\n", strlen("\r\n"), 0);
+    httpd_default_send(hd, fd, STR, strlen(STR), 0);
+#undef STR
+    free(arg);
+}
+
+esp_err_t async_get_handler(httpd_req_t *req)
+{
+#define STR "Hello World!"
+    httpd_resp_send(req, STR, strlen(STR));
+    /* Also register a HTTPD Work which sends the same data on the same
+     * socket again
+     */
+    struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
+    resp_arg->hd = req->handle;
+    resp_arg->fd = httpd_req_to_sockfd(req);
+    if (resp_arg->fd < 0) {
+        return ESP_FAIL;
+    }
+
+    ESP_LOGI(TAG, "Queuing work fd : %d", resp_arg->fd);
+    httpd_queue_work(req->handle, generate_async_resp, resp_arg);
+    return ESP_OK;
+#undef STR
+}
+
+
+httpd_uri_t basic_handlers[] = {
+    { .uri      = "/hello/type_html",
+      .method   = HTTP_GET,
+      .handler  = hello_type_get_handler,
+      .user_ctx = NULL,
+    },
+    { .uri      = "/hello",
+      .method   = HTTP_GET,
+      .handler  = hello_get_handler,
+      .user_ctx = NULL,
+    },
+    { .uri      = "/hello/status_500",
+      .method   = HTTP_GET,
+      .handler  = hello_status_get_handler,
+      .user_ctx = NULL,
+    },
+    { .uri      = "/echo",
+      .method   = HTTP_POST,
+      .handler  = echo_post_handler,
+      .user_ctx = NULL,
+    },
+    { .uri      = "/echo",
+      .method   = HTTP_PUT,
+      .handler  = echo_post_handler,
+      .user_ctx = NULL,
+    },
+    { .uri      = "/leftover_data",
+      .method   = HTTP_POST,
+      .handler  = leftover_data_post_handler,
+      .user_ctx = NULL,
+    },
+    { .uri      = "/adder",
+      .method   = HTTP_POST,
+      .handler  = adder_post_handler,
+      .user_ctx = NULL,
+    },
+    { .uri      = "/async_data",
+      .method   = HTTP_GET,
+      .handler  = async_get_handler,
+      .user_ctx = NULL,
+    }
+};
+
+int basic_handlers_no = sizeof(basic_handlers)/sizeof(httpd_uri_t);
+void register_basic_handlers(httpd_handle_t hd)
+{
+    int i;
+    ESP_LOGI(TAG, "Registering basic handlers");
+    ESP_LOGI(TAG, "No of handlers = %d", basic_handlers_no);
+    for (i = 0; i < basic_handlers_no; i++) {
+        if (httpd_register_uri_handler(hd, &basic_handlers[i]) != ESP_OK) {
+            ESP_LOGW(TAG, "register uri failed for %d", i);
+            return;
+        }
+    }
+    ESP_LOGI(TAG, "Success");
+}
+
+httpd_handle_t test_httpd_start()
+{
+    pre_start_mem = esp_get_free_heap_size();
+    httpd_handle_t hd;
+    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+    config.server_port = 1234;
+
+    /* This check should be a part of http_server */
+    config.max_open_sockets = (CONFIG_LWIP_MAX_SOCKETS - 3);
+
+    if (httpd_start(&hd, &config) == ESP_OK) {
+        ESP_LOGI(TAG, "Started HTTP server on port: '%d'", config.server_port);
+        ESP_LOGI(TAG, "Max URI handlers: '%d'", config.max_uri_handlers);
+        ESP_LOGI(TAG, "Max Open Sessions: '%d'", config.max_open_sockets);
+        ESP_LOGI(TAG, "Max Header Length: '%d'", HTTPD_MAX_REQ_HDR_LEN);
+        ESP_LOGI(TAG, "Max URI Length: '%d'", HTTPD_MAX_URI_LEN);
+        ESP_LOGI(TAG, "Max Stack Size: '%d'", config.stack_size);
+        return hd;
+    }
+    return NULL;
+}
+
+void test_httpd_stop(httpd_handle_t hd)
+{
+    httpd_stop(hd);
+    post_stop_mem = esp_get_free_heap_size();
+    ESP_LOGI(TAG, "HTTPD Stop: Current free memory: %d", post_stop_mem);
+}
+
+httpd_handle_t start_tests()
+{
+    httpd_handle_t hd = test_httpd_start();
+    if (hd) {
+        register_basic_handlers(hd);
+    }
+    return hd;
+}
+
+void stop_tests(httpd_handle_t hd)
+{
+    ESP_LOGI(TAG, "Stopping httpd");
+    test_httpd_stop(hd);
+}
diff --git a/examples/protocols/http_server/advanced_tests/scripts/test.py b/examples/protocols/http_server/advanced_tests/scripts/test.py
new file mode 100644
index 00000000..4f8f0fd4
--- /dev/null
+++ b/examples/protocols/http_server/advanced_tests/scripts/test.py
@@ -0,0 +1,863 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Utility for testing the web server. Test cases:
+# Assume the device supports 'n' simultaneous open sockets
+#
+# HTTP Server Tests
+#
+# 0. Firmware Settings:
+# - Create a dormant thread whose sole job is to call httpd_stop() when instructed
+# - Measure the following before httpd_start() is called:
+#     - current free memory
+#     - current free sockets
+# - Measure the same whenever httpd_stop is called
+# - Register maximum possible URI handlers: should be successful
+# - Register one more URI handler: should fail
+# - Deregister on URI handler: should be successful
+# - Register on more URI handler: should succeed
+# - Register separate handlers for /hello, /hello/type_html. Also
+#   ensure that /hello/type_html is registered BEFORE  /hello. (tests
+#   that largest matching URI is picked properly)
+# - Create URI handler /adder. Make sure it uses a custom free_ctx
+#   structure to free it up
+
+# 1. Using Standard Python HTTP Client
+# - simple GET on /hello (returns Hello World. Ensures that basic
+#   firmware tests are complete, or returns error)
+# - POST on /hello (should fail)
+# - PUT on /hello (should fail)
+# - simple POST on /echo (returns whatever the POST data)
+# - simple PUT on /echo (returns whatever the PUT data)
+# - GET on /echo (should fail)
+# - simple GET on /hello/type_html (returns Content type as text/html)
+# - simple GET on /hello/status_500 (returns HTTP status 500)
+# - simple GET on /false_uri (returns HTTP status 404)
+# - largest matching URI handler is picked is already verified because
+#   of /hello and /hello/type_html tests
+#
+#
+# 2. Session Tests
+# - Sessions + Pipelining basics:
+#    - Create max supported sessions
+#    - On session i,
+#          - send 3 back-to-back POST requests with data i on /adder
+#          - read back 3 responses. They should be i, 2i and 3i
+#    - Tests that
+#          - pipelining works
+#          - per-session context is maintained for all supported
+#            sessions
+#    - Close all sessions
+#
+# - Cleanup leftover data: Tests that the web server properly cleans
+#   up leftover data
+#    - Create a session
+#    - POST on /leftover_data with 52 bytes of data (data includes
+#      \r\n)(the handler only
+#      reads first 10 bytes and returns them, leaving the rest of the
+#      bytes unread)
+#    - GET on /hello (should return 'Hello World')
+#    - POST on /false_uri with 52  bytes of data (data includes \r\n)
+#      (should return HTTP 404)
+#    - GET on /hello (should return 'Hello World')
+#
+# - Test HTTPd Asynchronous response
+#   - Create a session
+#   - GET on /async_data
+#   - returns 'Hello World!' as a response
+#   - the handler schedules an async response, which generates a second
+#     response 'Hello Double World!'
+#
+# - Spillover test
+#    - Create max supported sessions with the web server
+#    - GET /hello on all the sessions (should return Hello World)
+#    - Create one more session, this should fail
+#    - GET /hello on all the sessions (should return Hello World)
+#
+# - Timeout test
+#    - Create a session and only Send 'GE' on the same (simulates a
+#      client that left the network halfway through a request)
+#    - Wait for recv-wait-timeout
+#    - Server should automatically close the socket
+
+
+############# TODO TESTS #############
+
+# 3. Stress Tests
+#
+# - httperf
+#     - Run the following httperf command:
+#     httperf --server=10.31.130.126 --wsess=8,50,0.5 --rate 8 --burst-length 2
+#
+#     - The above implies that the test suite will open
+#           - 8 simultaneous connections with the server
+#           - the rate of opening the sessions will be 8 per sec. So in our
+#             case, a new connection will be opened every 0.2 seconds for 1 second
+#           - The burst length 2 indicates that 2 requests will be sent
+#             simultaneously on the same connection in a single go
+#           - 0.5 seconds is the time between sending out 2 bursts
+#           - 50 is the total number of requests that will be sent out
+#
+#     - So in the above example, the test suite will open 8
+#       connections, each separated by 0.2 seconds. On each connection
+#       it will send 2 requests in a single burst. The bursts on a
+#       single connection will be separated by 0.5 seconds. A total of
+#       25 bursts (25 x 2 = 50) will be sent out
+
+# 4. Leak Tests
+# - Simple Leak test
+#    - Simple GET on /hello/restart (returns success, stop web server, measures leaks, restarts webserver)
+#    - Simple GET on /hello/restart_results (returns the leak results)
+# - Leak test with open sockets
+#    - Open 8 sessions
+#    - Simple GET on /hello/restart (returns success, stop web server,
+#      measures leaks, restarts webserver)
+#    - All sockets should get closed
+#    - Simple GET on /hello/restart_results (returns the leak results)
+
+
+from __future__ import division
+from __future__ import print_function
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import range
+from builtins import object
+import threading
+import socket
+import time
+import argparse
+import http.client
+import sys
+import string
+import random
+import Utility
+
+_verbose_ = False
+
+class Session(object):
+    def __init__(self, addr, port, timeout = 15):
+        self.client = socket.create_connection((addr, int(port)), timeout = timeout)
+        self.target = addr
+        self.status = 0
+        self.encoding = ''
+        self.content_type = ''
+        self.content_len = 0
+
+    def send_err_check(self, request, data=None):
+        rval = True
+        try:
+            self.client.sendall(request.encode());
+            if data:
+                self.client.sendall(data.encode())
+        except socket.error as err:
+            self.client.close()
+            Utility.console_log("Socket Error in send :", err)
+            rval = False
+        return rval
+
+    def send_get(self, path, headers=None):
+        request = "GET " + path + " HTTP/1.1\r\nHost: " + self.target
+        if headers:
+            for field, value in headers.items():
+                request += "\r\n"+field+": "+value
+        request += "\r\n\r\n"
+        return self.send_err_check(request)
+
+    def send_put(self, path, data, headers=None):
+        request = "PUT " + path +  " HTTP/1.1\r\nHost: " + self.target
+        if headers:
+            for field, value in headers.items():
+                request += "\r\n"+field+": "+value
+        request += "\r\nContent-Length: " + str(len(data)) +"\r\n\r\n"
+        return self.send_err_check(request, data)
+
+    def send_post(self, path, data, headers=None):
+        request = "POST " + path +  " HTTP/1.1\r\nHost: " + self.target
+        if headers:
+            for field, value in headers.items():
+                request += "\r\n"+field+": "+value
+        request += "\r\nContent-Length: " + str(len(data)) +"\r\n\r\n"
+        return self.send_err_check(request, data)
+
+    def read_resp_hdrs(self):
+        try:
+            state = 'nothing'
+            resp_read = ''
+            while True:
+                char = self.client.recv(1).decode()
+                if char == '\r' and state == 'nothing':
+                    state = 'first_cr'
+                elif char == '\n' and state == 'first_cr':
+                    state = 'first_lf'
+                elif char == '\r' and state == 'first_lf':
+                    state = 'second_cr'
+                elif char == '\n' and state == 'second_cr':
+                    state = 'second_lf'
+                else:
+                    state = 'nothing'
+                resp_read += char
+                if state == 'second_lf':
+                    break
+            # Handle first line
+            line_hdrs = resp_read.splitlines()
+            line_comp = line_hdrs[0].split()
+            self.status = line_comp[1]
+            del line_hdrs[0]
+            self.encoding = ''
+            self.content_type = ''
+            headers = dict()
+            # Process other headers
+            for h in range(len(line_hdrs)):
+                line_comp = line_hdrs[h].split(':')
+                if line_comp[0] == 'Content-Length':
+                    self.content_len = int(line_comp[1])
+                if line_comp[0] == 'Content-Type':
+                    self.content_type = line_comp[1].lstrip()
+                if line_comp[0] == 'Transfer-Encoding':
+                    self.encoding = line_comp[1].lstrip()
+                if len(line_comp) == 2:
+                    headers[line_comp[0]] = line_comp[1].lstrip()
+            return headers
+        except socket.error as err:
+            self.client.close()
+            Utility.console_log("Socket Error in recv :", err)
+            return None
+
+    def read_resp_data(self):
+        try:
+            read_data = ''
+            if self.encoding != 'chunked':
+                while len(read_data) != self.content_len:
+                    read_data += self.client.recv(self.content_len).decode()
+            else:
+                chunk_data_buf = ''
+                while (True):
+                    # Read one character into temp  buffer
+                    read_ch = self.client.recv(1)
+                    # Check CRLF
+                    if (read_ch == '\r'):
+                        read_ch = self.client.recv(1).decode()
+                        if (read_ch == '\n'):
+                            # If CRLF decode length of chunk
+                            chunk_len = int(chunk_data_buf, 16)
+                            # Keep adding to contents
+                            self.content_len += chunk_len
+                            rem_len = chunk_len
+                            while (rem_len):
+                                new_data = self.client.recv(rem_len)
+                                read_data += new_data
+                                rem_len -= len(new_data)
+                            chunk_data_buf = ''
+                            # Fetch remaining CRLF
+                            if self.client.recv(2) != "\r\n":
+                                # Error in packet
+                                Utility.console_log("Error in chunked data")
+                                return None
+                            if not chunk_len:
+                                # If last chunk
+                                break
+                            continue
+                        chunk_data_buf += '\r'
+                    # If not CRLF continue appending
+                    # character to chunked data buffer
+                    chunk_data_buf += read_ch
+            return read_data
+        except socket.error as err:
+            self.client.close()
+            Utility.console_log("Socket Error in recv :", err)
+            return None
+
+    def close(self):
+        self.client.close()
+
+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
+
+class adder_thread (threading.Thread):
+    def __init__(self, id, dut, port):
+        threading.Thread.__init__(self)
+        self.id = id
+        self.dut = dut
+        self.depth = 3
+        self.session = Session(dut, port)
+
+    def run(self):
+        self.response = []
+
+        # Pipeline 3 requests
+        if (_verbose_):
+            Utility.console_log("   Thread: Using adder start " + str(self.id))
+
+        for _ in range(self.depth):
+            self.session.send_post('/adder', str(self.id))
+            time.sleep(2)
+
+        for _ in range(self.depth):
+            self.session.read_resp_hdrs()
+            self.response.append(self.session.read_resp_data())
+
+    def adder_result(self):
+        if len(self.response) != self.depth:
+            Utility.console_log("Error : missing response packets")
+            return False
+        for i in range(len(self.response)):
+            if not test_val("Thread" + str(self.id) + " response[" + str(i) + "]",
+                            str(self.id * (i + 1)), str(self.response[i])):
+                return False
+        return True
+
+    def close(self):
+        self.session.close()
+
+def get_hello(dut, port):
+    # GET /hello should return 'Hello World!'
+    Utility.console_log("[test] GET /hello returns 'Hello World!' =>", end=' ')
+    conn = http.client.HTTPConnection(dut, int(port), timeout=15)
+    conn.request("GET", "/hello")
+    resp = conn.getresponse()
+    if not test_val("status_code", 200, resp.status):
+        conn.close()
+        return False
+    if not test_val("data", "Hello World!", resp.read().decode()):
+        conn.close()
+        return False
+    if not test_val("data", "text/html", resp.getheader('Content-Type')):
+        conn.close()
+        return False
+    Utility.console_log("Success")
+    conn.close()
+    return True
+
+def put_hello(dut, port):
+    # PUT /hello returns 405'
+    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()
+    if not test_val("status_code", 405, resp.status):
+        conn.close()
+        return False
+    Utility.console_log("Success")
+    conn.close()
+    return True
+
+def post_hello(dut, port):
+    # POST /hello returns 405'
+    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()
+    if not test_val("status_code", 405, resp.status):
+        conn.close()
+        return False
+    Utility.console_log("Success")
+    conn.close()
+    return True
+
+def post_echo(dut, port):
+    # POST /echo echoes data'
+    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()
+    if not test_val("status_code", 200, resp.status):
+        conn.close()
+        return False
+    if not test_val("data", "Hello", resp.read().decode()):
+        conn.close()
+        return False
+    Utility.console_log("Success")
+    conn.close()
+    return True
+
+def put_echo(dut, port):
+    # PUT /echo echoes data'
+    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()
+    if not test_val("status_code", 200, resp.status):
+        conn.close()
+        return False
+    if not test_val("data", "Hello", resp.read().decode()):
+        conn.close()
+        return False
+    Utility.console_log("Success")
+    conn.close()
+    return True
+
+def get_echo(dut, port):
+    # GET /echo returns 404'
+    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()
+    if not test_val("status_code", 405, resp.status):
+        conn.close()
+        return False
+    Utility.console_log("Success")
+    conn.close()
+    return True
+
+def get_hello_type(dut, port):
+    # GET /hello/type_html returns text/html as Content-Type'
+    Utility.console_log("[test] GET /hello/type_html has Content-Type of text/html =>", end=' ')
+    conn = http.client.HTTPConnection(dut, int(port), timeout=15)
+    conn.request("GET", "/hello/type_html")
+    resp = conn.getresponse()
+    if not test_val("status_code", 200, resp.status):
+        conn.close()
+        return False
+    if not test_val("data", "Hello World!", resp.read().decode()):
+        conn.close()
+        return False
+    if not test_val("data", "text/html", resp.getheader('Content-Type')):
+        conn.close()
+        return False
+    Utility.console_log("Success")
+    conn.close()
+    return True
+
+def get_hello_status(dut, port):
+    # GET /hello/status_500 returns status 500'
+    Utility.console_log("[test] GET /hello/status_500 returns status 500 =>", end=' ')
+    conn = http.client.HTTPConnection(dut, int(port), timeout=15)
+    conn.request("GET", "/hello/status_500")
+    resp = conn.getresponse()
+    if not test_val("status_code", 500, resp.status):
+        conn.close()
+        return False
+    Utility.console_log("Success")
+    conn.close()
+    return True
+
+def get_false_uri(dut, port):
+    # GET /false_uri returns status 404'
+    Utility.console_log("[test] GET /false_uri returns status 404 =>", end=' ')
+    conn = http.client.HTTPConnection(dut, int(port), timeout=15)
+    conn.request("GET", "/false_uri")
+    resp = conn.getresponse()
+    if not test_val("status_code", 404, resp.status):
+        conn.close()
+        return False
+    Utility.console_log("Success")
+    conn.close()
+    return True
+
+def parallel_sessions_adder(dut, port, max_sessions):
+    # POSTs on /adder in parallel sessions
+    Utility.console_log("[test] POST {pipelined} on /adder in " + str(max_sessions) + " sessions =>", end=' ')
+    t = []
+    # Create all sessions
+    for i in range(max_sessions):
+        t.append(adder_thread(i, dut, port))
+
+    for i in range(len(t)):
+        t[i].start()
+
+    for i in range(len(t)):
+        t[i].join()
+
+    res = True
+    for i in range(len(t)):
+        if not test_val("Thread" + str(i) + " Failed", t[i].adder_result(), True):
+            res = False
+        t[i].close()
+    if (res):
+        Utility.console_log("Success")
+    return res
+
+def async_response_test(dut, port):
+    # Test that an asynchronous work is executed in the HTTPD's context
+    # This is tested by reading two responses over the same session
+    Utility.console_log("[test] Test HTTPD Work Queue (Async response) =>", end=' ')
+    s = Session(dut, port)
+
+    s.send_get('/async_data')
+    s.read_resp_hdrs()
+    if not test_val("First Response", "Hello World!", s.read_resp_data()):
+        s.close()
+        return False
+    s.read_resp_hdrs()
+    if not test_val("Second Response", "Hello Double World!", s.read_resp_data()):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def leftover_data_test(dut, port):
+    # Leftover data in POST is purged (valid and invalid URIs)
+    Utility.console_log("[test] Leftover data in POST is purged (valid and invalid URIs) =>", end=' ')
+    s = http.client.HTTPConnection(dut + ":" + port, timeout=15)
+
+    s.request("POST", url='/leftover_data', body="abcdefghijklmnopqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz")
+    resp = s.getresponse()
+    if not test_val("Partial data", "abcdefghij", resp.read().decode()):
+        s.close()
+        return False
+
+    s.request("GET", url='/hello')
+    resp = s.getresponse()
+    if not test_val("Hello World Data", "Hello World!", resp.read().decode()):
+        s.close()
+        return False
+
+    s.request("POST", url='/false_uri', body="abcdefghijklmnopqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz")
+    resp = s.getresponse()
+    if not test_val("False URI Status", str(404), str(resp.status)):
+        s.close()
+        return False
+    resp.read()
+
+    s.request("GET", url='/hello')
+    resp = s.getresponse()
+    if not test_val("Hello World Data", "Hello World!", resp.read().decode()):
+        s.close()
+        return False
+
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def spillover_session(dut, port, max_sess):
+    # Session max_sess_sessions + 1 is rejected
+    Utility.console_log("[test] Session max_sess_sessions (" + str(max_sess) + ") + 1 is rejected =>", end=' ')
+    s = []
+    _verbose_ = True
+    for i in range(max_sess + 1):
+        if (_verbose_):
+            Utility.console_log("Executing " + str(i))
+        try:
+            a = http.client.HTTPConnection(dut + ":" + port, timeout=15)
+            a.request("GET", url='/hello')
+            resp = a.getresponse()
+            if not test_val("Connection " + str(i), "Hello World!", resp.read().decode()):
+                a.close()
+                break
+            s.append(a)
+        except:
+            if (_verbose_):
+                Utility.console_log("Connection " + str(i) + " rejected")
+            a.close()
+            break
+
+    # Close open connections
+    for a in s:
+        a.close()
+
+    # Check if number of connections is equal to max_sess
+    Utility.console_log(["Fail","Success"][len(s) == max_sess])
+    return (len(s) == max_sess)
+
+def recv_timeout_test(dut, port):
+    Utility.console_log("[test] Timeout occurs if partial packet sent =>", end=' ')
+    s = Session(dut, port)
+    s.client.sendall(b"GE")
+    s.read_resp_hdrs()
+    resp = s.read_resp_data()
+    if not test_val("Request Timeout", "Server closed this connection", resp):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def packet_size_limit_test(dut, port, test_size):
+    Utility.console_log("[test] send size limit test =>", end=' ')
+    retry = 5
+    while (retry):
+        retry -= 1
+        Utility.console_log("data size = ", test_size)
+        s = http.client.HTTPConnection(dut + ":" + port, timeout=15)
+        random_data = ''.join(string.printable[random.randint(0,len(string.printable))-1] for _ in list(range(test_size)))
+        path = "/echo"
+        s.request("POST", url=path, body=random_data)
+        resp = s.getresponse()
+        if not test_val("Error", "200", 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:
+                Utility.console_log("Unexpected error")
+            s.close()
+            Utility.console_log("Retry...")
+            continue
+        resp = resp.read().decode()
+        result = (resp == random_data)
+        if not result:
+            test_val("Data size", str(len(random_data)), str(len(resp)))
+            s.close()
+            Utility.console_log("Retry...")
+            continue
+        s.close()
+        Utility.console_log("Success")
+        return True
+    Utility.console_log("Failed")
+    return False
+
+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
+    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()
+    if not test_val("Server Error", "500", s.status):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def code_501_method_not_impl(dut, port):
+    Utility.console_log("[test] 501 Method Not Implemented =>", end=' ')
+    s = Session(dut, port)
+    path = "/hello"
+    s.client.sendall(("ABC " + path + " HTTP/1.1\r\nHost: " + dut + "\r\n\r\n").encode())
+    s.read_resp_hdrs()
+    resp = s.read_resp_data()
+    # Presently server sends back 400 Bad Request
+    #if not test_val("Server Error", "501", s.status):
+        #s.close()
+        #return False
+    if not test_val("Server Error", "400", s.status):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def code_505_version_not_supported(dut, port):
+    Utility.console_log("[test] 505 Version Not Supported =>", end=' ')
+    s = Session(dut, port)
+    path = "/hello"
+    s.client.sendall(("GET " + path + " HTTP/2.0\r\nHost: " + dut + "\r\n\r\n").encode())
+    s.read_resp_hdrs()
+    resp = s.read_resp_data()
+    if not test_val("Server Error", "505", s.status):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def code_400_bad_request(dut, port):
+    Utility.console_log("[test] 400 Bad Request =>", end=' ')
+    s = Session(dut, port)
+    path = "/hello"
+    s.client.sendall(("XYZ " + path + " HTTP/1.1\r\nHost: " + dut + "\r\n\r\n").encode())
+    s.read_resp_hdrs()
+    resp = s.read_resp_data()
+    if not test_val("Client Error", "400", s.status):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def code_404_not_found(dut, port):
+    Utility.console_log("[test] 404 Not Found =>", end=' ')
+    s = Session(dut, port)
+    path = "/dummy"
+    s.client.sendall(("GET " + path + " HTTP/1.1\r\nHost: " + dut + "\r\n\r\n").encode())
+    s.read_resp_hdrs()
+    resp = s.read_resp_data()
+    if not test_val("Client Error", "404", s.status):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def code_405_method_not_allowed(dut, port):
+    Utility.console_log("[test] 405 Method Not Allowed =>", end=' ')
+    s = Session(dut, port)
+    path = "/hello"
+    s.client.sendall(("POST " + path + " HTTP/1.1\r\nHost: " + dut + "\r\n\r\n").encode())
+    s.read_resp_hdrs()
+    resp = s.read_resp_data()
+    if not test_val("Client Error", "405", s.status):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def code_408_req_timeout(dut, port):
+    Utility.console_log("[test] 408 Request Timeout =>", end=' ')
+    s = Session(dut, port)
+    s.client.sendall(("POST /echo HTTP/1.1\r\nHost: " + dut + "\r\nContent-Length: 10\r\n\r\nABCD").encode())
+    s.read_resp_hdrs()
+    resp = s.read_resp_data()
+    if not test_val("Client Error", "408", s.status):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def code_411_length_required(dut, port):
+    Utility.console_log("[test] 411 Length Required =>", end=' ')
+    s = Session(dut, port)
+    path = "/echo"
+    s.client.sendall(("POST " + path + " HTTP/1.1\r\nHost: " + dut + "\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n").encode())
+    s.read_resp_hdrs()
+    resp = s.read_resp_data()
+    # Presently server sends back 400 Bad Request
+    #if not test_val("Client Error", "411", s.status):
+        #s.close()
+        #return False
+    if not test_val("Client Error", "400", s.status):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+def send_getx_uri_len(dut, port, length):
+    s = Session(dut, port)
+    method = "GET "
+    version = " HTTP/1.1\r\n"
+    path = "/"+"x"*(length - len(method) - len(version) - len("/"))
+    s.client.sendall(method.encode())
+    time.sleep(1)
+    s.client.sendall(path.encode())
+    time.sleep(1)
+    s.client.sendall((version + "Host: " + dut + "\r\n\r\n").encode())
+    s.read_resp_hdrs()
+    resp = s.read_resp_data()
+    s.close()
+    return s.status
+
+def code_414_uri_too_long(dut, port, max_uri_len):
+    Utility.console_log("[test] 414 URI Too Long =>", end=' ')
+    status = send_getx_uri_len(dut, port, max_uri_len)
+    if not test_val("Client Error", "404", status):
+        return False
+    status = send_getx_uri_len(dut, port, max_uri_len + 1)
+    if not test_val("Client Error", "414", status):
+        return False
+    Utility.console_log("Success")
+    return True
+
+def send_postx_hdr_len(dut, port, length):
+    s = Session(dut, port)
+    path = "/echo"
+    host = "Host: " + dut
+    custom_hdr_field = "\r\nCustom: "
+    custom_hdr_val = "x"*(length - len(host) - len(custom_hdr_field) - len("\r\n\r\n") + len("0"))
+    request = ("POST " + path + " HTTP/1.1\r\n" + host + custom_hdr_field + custom_hdr_val + "\r\n\r\n").encode()
+    s.client.sendall(request[:length//2])
+    time.sleep(1)
+    s.client.sendall(request[length//2:])
+    hdr = s.read_resp_hdrs()
+    resp = s.read_resp_data()
+    s.close()
+    if "Custom" in hdr:
+        return (hdr["Custom"] == custom_hdr_val), resp
+    return False, s.status
+
+def code_431_hdr_too_long(dut, port, max_hdr_len):
+    Utility.console_log("[test] 431 Header Too Long =>", end=' ')
+    res, status = send_postx_hdr_len(dut, port, max_hdr_len)
+    if not res:
+        return False
+    res, status = send_postx_hdr_len(dut, port, max_hdr_len + 1)
+    if not test_val("Client Error", "431", status):
+        return False
+    Utility.console_log("Success")
+    return True
+
+def test_upgrade_not_supported(dut, port):
+    Utility.console_log("[test] Upgrade Not Supported =>", end=' ')
+    s = Session(dut, port)
+    path = "/hello"
+    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()
+    resp = s.read_resp_data()
+    if not test_val("Client Error", "200", s.status):
+        s.close()
+        return False
+    s.close()
+    Utility.console_log("Success")
+    return True
+
+if __name__ == '__main__':
+    ########### Execution begins here...
+    # Configuration
+    # Max number of threads/sessions
+    max_sessions = 7
+    max_uri_len = 512
+    max_hdr_len = 512
+
+    parser = argparse.ArgumentParser(description='Run HTTPD Test')
+    parser.add_argument('-4','--ipv4', help='IPv4 address')
+    parser.add_argument('-6','--ipv6', help='IPv6 address')
+    parser.add_argument('-p','--port', help='Port')
+    args = vars(parser.parse_args())
+
+    dut4 = args['ipv4']
+    dut6 = args['ipv6']
+    port = args['port']
+    dut  = dut4
+
+    _verbose_ = True
+
+    Utility.console_log("### Basic HTTP Client Tests")
+    get_hello(dut, port)
+    post_hello(dut, port)
+    put_hello(dut, port)
+    post_echo(dut, port)
+    get_echo(dut, port)
+    put_echo(dut, port)
+    get_hello_type(dut, port)
+    get_hello_status(dut, port)
+    get_false_uri(dut, port)
+
+    Utility.console_log("### Error code tests")
+    code_500_server_error_test(dut, port)
+    code_501_method_not_impl(dut, port)
+    code_505_version_not_supported(dut, port)
+    code_400_bad_request(dut, port)
+    code_404_not_found(dut, port)
+    code_405_method_not_allowed(dut, port)
+    code_408_req_timeout(dut, port)
+    code_414_uri_too_long(dut, port, max_uri_len)
+    code_431_hdr_too_long(dut, port, max_hdr_len)
+    test_upgrade_not_supported(dut, port)
+
+    # Not supported yet (Error on chunked request)
+    ###code_411_length_required(dut, port)
+
+    Utility.console_log("### Sessions and Context Tests")
+    parallel_sessions_adder(dut, port, max_sessions)
+    leftover_data_test(dut, port)
+    async_response_test(dut, port)
+    spillover_session(dut, port, max_sessions)
+    recv_timeout_test(dut, port)
+    packet_size_limit_test(dut, port, 50*1024)
+    get_hello(dut, port)
+
+    sys.exit()
diff --git a/examples/protocols/http_server/persistent_sockets/CMakeLists.txt b/examples/protocols/http_server/persistent_sockets/CMakeLists.txt
new file mode 100644
index 00000000..2d453b67
--- /dev/null
+++ b/examples/protocols/http_server/persistent_sockets/CMakeLists.txt
@@ -0,0 +1,7 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(persistent_sockets)
+
diff --git a/examples/protocols/http_server/persistent_sockets/Makefile b/examples/protocols/http_server/persistent_sockets/Makefile
new file mode 100644
index 00000000..9c178022
--- /dev/null
+++ b/examples/protocols/http_server/persistent_sockets/Makefile
@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := persistent_sockets
+
+include $(IDF_PATH)/make/project.mk
+
diff --git a/examples/protocols/http_server/persistent_sockets/README.md b/examples/protocols/http_server/persistent_sockets/README.md
new file mode 100644
index 00000000..b7517d8f
--- /dev/null
+++ b/examples/protocols/http_server/persistent_sockets/README.md
@@ -0,0 +1,18 @@
+# HTTPD Server Persistant Sockets Example
+
+The Example consists of HTTPD server persistent sockets demo.
+This sort of persistancy enables the server to have independent sessions/contexts per client.
+
+* Configure the project using "make menuconfig" and goto :
+    * Example Configuration ->
+        1. WIFI SSID: WIFI network to which your PC is also connected to.
+        2. WIFI Password: WIFI password
+
+* In order to test the HTTPD server persistent sockets demo :
+    1. compile and burn the firmware "make flash"
+    2. run "make monitor" and note down the IP assigned to your ESP module. The default port is 80
+    3. run the test script "python2 scripts/adder.py \<IP\> \<port\> \<N\>"
+        * the provided test script sends (POST) numbers from 1 to N to the server which has a URI POST handler for adding these numbers into an accumulator that is valid throughout the lifetime of the connection socket, hence persistent
+        * the script does a GET before closing and displays the final value of the accumulator
+
+See the README.md file in the upper level 'examples' directory for more information about examples.
diff --git a/examples/protocols/http_server/persistent_sockets/http_server_persistence_test.py b/examples/protocols/http_server/persistent_sockets/http_server_persistence_test.py
new file mode 100644
index 00000000..79f70624
--- /dev/null
+++ b/examples/protocols/http_server/persistent_sockets/http_server_persistence_test.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+from builtins import str
+from builtins import range
+import imp
+import re
+import os
+import sys
+import string
+import random
+import socket
+
+# This environment variable is expected on the host machine
+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)
+
+# When running on local machine execute the following before running this script
+# > make app bootloader
+# > make print_flash_cmd | tail -n 1 > build/download.config
+# > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
+
+import TinyFW
+import IDF
+import Utility
+
+# Import client module
+expath = os.path.dirname(os.path.realpath(__file__))
+client = imp.load_source("client", expath + "/scripts/adder.py")
+
+@IDF.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_http_server_persistence(env, extra_data):
+    # Acquire DUT
+    dut1 = env.get_dut("http_server", "examples/protocols/http_server/persistent_sockets")
+
+    # Get binary file
+    binary_file = os.path.join(dut1.app.binary_path, "persistent_sockets.bin")
+    bin_size = os.path.getsize(binary_file)
+    IDF.log_performance("http_server_bin_size", "{}KB".format(bin_size//1024))
+    IDF.check_performance("http_server_bin_size", bin_size//1024)
+
+    # Upload binary and start testing
+    Utility.console_log("Starting http_server persistance test app")
+    dut1.start_app()
+
+    # Parse IP address of STA
+    Utility.console_log("Waiting to connect with AP")
+    got_ip   = dut1.expect(re.compile(r"(?:[\s\S]*)Got IP: '(\d+.\d+.\d+.\d+)'"), timeout=120)[0]
+    got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Starting server on port: '(\d+)'"), timeout=30)[0]
+
+    Utility.console_log("Got IP   : " + got_ip)
+    Utility.console_log("Got Port : " + got_port)
+
+    # Expected Logs
+    dut1.expect("Registering URI handlers", timeout=30)
+
+    # Run test script
+    conn = client.start_session(got_ip, got_port)
+    visitor = 0
+    adder = 0
+
+    # Test PUT request and initialize session context
+    num = random.randint(0,100)
+    client.putreq(conn, "/adder", str(num))
+    visitor += 1
+    dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
+    dut1.expect("/adder PUT handler read " + str(num), timeout=30)
+    dut1.expect("PUT allocating new session", timeout=30)
+
+    # Retest PUT request and change session context value
+    num = random.randint(0,100)
+    Utility.console_log("Adding: " + str(num))
+    client.putreq(conn, "/adder", str(num))
+    visitor += 1
+    adder += num
+    dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
+    dut1.expect("/adder PUT handler read " + str(num), timeout=30)
+    try:
+        # Re allocation shouldn't happen
+        dut1.expect("PUT allocating new session", timeout=30)
+        # Not expected
+        raise RuntimeError
+    except:
+        # As expected
+        pass
+
+    # Test POST request and session persistence
+    random_nums = [random.randint(0,100) for _ in range(100)]
+    for num in random_nums:
+        Utility.console_log("Adding: " + str(num))
+        client.postreq(conn, "/adder", str(num))
+        visitor += 1
+        adder += num
+        dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
+        dut1.expect("/adder handler read " + str(num), timeout=30)
+
+    # Test GET request and session persistence
+    Utility.console_log("Matching final sum: " + str(adder))
+    if client.getreq(conn, "/adder").decode() != str(adder):
+        raise RuntimeError
+    visitor += 1
+    dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
+    dut1.expect("/adder GET handler send " + str(adder), timeout=30)
+
+    Utility.console_log("Ending session")
+    # Close connection and check for invocation of context "Free" function
+    client.end_session(conn)
+    dut1.expect("/adder Free Context function called", timeout=30)
+
+    Utility.console_log("Validating user context data")
+    # Start another session to check user context data
+    conn2 = client.start_session(got_ip, got_port)
+    num = random.randint(0,100)
+    client.putreq(conn, "/adder", str(num))
+    visitor += 1
+    dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
+    dut1.expect("/adder PUT handler read " + str(num), timeout=30)
+    dut1.expect("PUT allocating new session", timeout=30)
+    client.end_session(conn)
+    dut1.expect("/adder Free Context function called", timeout=30)
+
+if __name__ == '__main__':
+    test_examples_protocol_http_server_persistence()
diff --git a/examples/protocols/http_server/persistent_sockets/main/CMakeLists.txt b/examples/protocols/http_server/persistent_sockets/main/CMakeLists.txt
new file mode 100644
index 00000000..85970762
--- /dev/null
+++ b/examples/protocols/http_server/persistent_sockets/main/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(COMPONENT_SRCS "main.c")
+set(COMPONENT_ADD_INCLUDEDIRS ".")
+
+register_component()
diff --git a/examples/protocols/http_server/persistent_sockets/main/Kconfig.projbuild b/examples/protocols/http_server/persistent_sockets/main/Kconfig.projbuild
new file mode 100644
index 00000000..9e2813c6
--- /dev/null
+++ b/examples/protocols/http_server/persistent_sockets/main/Kconfig.projbuild
@@ -0,0 +1,16 @@
+menu "Example Configuration"
+
+config WIFI_SSID
+    string "WiFi SSID"
+    default "myssid"
+    help
+        SSID (network name) for the example to connect to.
+
+config WIFI_PASSWORD
+    string "WiFi Password"
+    default "mypassword"
+    help
+        WiFi password (WPA or WPA2) for the example to use.
+        Can be left blank if the network has no security set.
+
+endmenu
diff --git a/examples/protocols/http_server/persistent_sockets/main/component.mk b/examples/protocols/http_server/persistent_sockets/main/component.mk
new file mode 100644
index 00000000..0b9d7585
--- /dev/null
+++ b/examples/protocols/http_server/persistent_sockets/main/component.mk
@@ -0,0 +1,5 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
diff --git a/examples/protocols/http_server/persistent_sockets/main/main.c b/examples/protocols/http_server/persistent_sockets/main/main.c
new file mode 100644
index 00000000..b3535592
--- /dev/null
+++ b/examples/protocols/http_server/persistent_sockets/main/main.c
@@ -0,0 +1,252 @@
+/* Persistent Sockets Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <esp_wifi.h>
+#include <esp_event_loop.h>
+#include <esp_log.h>
+#include <esp_system.h>
+#include <nvs_flash.h>
+
+#include <esp_http_server.h>
+
+/* An example to demonstrate persistent sockets, with context maintained across
+ * multiple requests on that socket.
+ * The examples use simple WiFi configuration that you can set via 'make menuconfig'.
+ * If you'd rather not, just change the below entries to strings with
+ * the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
+ */
+#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
+#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
+
+static const char *TAG="APP";
+
+/* Function to free context */
+void adder_free_func(void *ctx)
+{
+    ESP_LOGI(TAG, "/adder Free Context function called");
+    free(ctx);
+}
+
+/* This handler keeps accumulating data that is posted to it into a per
+ * socket/session context. And returns the result.
+ */
+esp_err_t adder_post_handler(httpd_req_t *req)
+{
+    /* Log total visitors */
+    unsigned *visitors = (unsigned *)req->user_ctx;
+    ESP_LOGI(TAG, "/adder visitor count = %d", ++(*visitors));
+
+    char buf[10];
+    char outbuf[50];
+    int  ret;
+
+    /* Read data received in the request */
+    ret = httpd_req_recv(req, buf, sizeof(buf));
+    if (ret <= 0) {
+        if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
+            httpd_resp_send_408(req);
+        }
+        return ESP_FAIL;
+    }
+
+    buf[ret] = '\0';
+    int val = atoi(buf);
+    ESP_LOGI(TAG, "/adder handler read %d", val);
+
+    /* Create session's context if not already available */
+    if (! req->sess_ctx) {
+        ESP_LOGI(TAG, "/adder allocating new session");
+        req->sess_ctx = malloc(sizeof(int));
+        req->free_ctx = adder_free_func;
+        *(int *)req->sess_ctx = 0;
+    }
+
+    /* Add the received data to the context */
+    int *adder = (int *)req->sess_ctx;
+    *adder += val;
+
+    /* Respond with the accumulated value */
+    snprintf(outbuf, sizeof(outbuf),"%d", *adder);
+    httpd_resp_send(req, outbuf, strlen(outbuf));
+    return ESP_OK;
+}
+
+/* This handler gets the present value of the accumulator */
+esp_err_t adder_get_handler(httpd_req_t *req)
+{
+    /* Log total visitors */
+    unsigned *visitors = (unsigned *)req->user_ctx;
+    ESP_LOGI(TAG, "/adder visitor count = %d", ++(*visitors));
+
+    char outbuf[50];
+
+    /* Create session's context if not already available */
+    if (! req->sess_ctx) {
+        ESP_LOGI(TAG, "/adder GET allocating new session");
+        req->sess_ctx = malloc(sizeof(int));
+        req->free_ctx = adder_free_func;
+        *(int *)req->sess_ctx = 0;
+    }
+    ESP_LOGI(TAG, "/adder GET handler send %d", *(int *)req->sess_ctx);
+
+    /* Respond with the accumulated value */
+    snprintf(outbuf, sizeof(outbuf),"%d", *((int *)req->sess_ctx));
+    httpd_resp_send(req, outbuf, strlen(outbuf));
+    return ESP_OK;
+}
+
+/* This handler resets the value of the accumulator */
+esp_err_t adder_put_handler(httpd_req_t *req)
+{
+    /* Log total visitors */
+    unsigned *visitors = (unsigned *)req->user_ctx;
+    ESP_LOGI(TAG, "/adder visitor count = %d", ++(*visitors));
+
+    char buf[10];
+    char outbuf[50];
+    int  ret;
+
+    /* Read data received in the request */
+    ret = httpd_req_recv(req, buf, sizeof(buf));
+    if (ret <= 0) {
+        if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
+            httpd_resp_send_408(req);
+        }
+        return ESP_FAIL;
+    }
+
+    buf[ret] = '\0';
+    int val = atoi(buf);
+    ESP_LOGI(TAG, "/adder PUT handler read %d", val);
+
+    /* Create session's context if not already available */
+    if (! req->sess_ctx) {
+        ESP_LOGI(TAG, "/adder PUT allocating new session");
+        req->sess_ctx = malloc(sizeof(int));
+        req->free_ctx = adder_free_func;
+    }
+    *(int *)req->sess_ctx = val;
+
+    /* Respond with the reset value */
+    snprintf(outbuf, sizeof(outbuf),"%d", *((int *)req->sess_ctx));
+    httpd_resp_send(req, outbuf, strlen(outbuf));
+    return ESP_OK;
+}
+
+/* Maintain a variable which stores the number of times
+ * the "/adder" URI has been visited */
+static unsigned visitors = 0;
+
+httpd_uri_t adder_post = {
+    .uri      = "/adder",
+    .method   = HTTP_POST,
+    .handler  = adder_post_handler,
+    .user_ctx = &visitors
+};
+
+httpd_uri_t adder_get = {
+    .uri      = "/adder",
+    .method   = HTTP_GET,
+    .handler  = adder_get_handler,
+    .user_ctx = &visitors
+};
+
+httpd_uri_t adder_put = {
+    .uri      = "/adder",
+    .method   = HTTP_PUT,
+    .handler  = adder_put_handler,
+    .user_ctx = &visitors
+};
+
+httpd_handle_t start_webserver(void)
+{
+    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+    // Start the httpd server
+    ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
+    httpd_handle_t server;
+
+    if (httpd_start(&server, &config) == ESP_OK) {
+        // Set URI handlers
+        ESP_LOGI(TAG, "Registering URI handlers");
+        httpd_register_uri_handler(server, &adder_get);
+        httpd_register_uri_handler(server, &adder_put);
+        httpd_register_uri_handler(server, &adder_post);
+        return server;
+    }
+
+    ESP_LOGI(TAG, "Error starting server!");
+    return NULL;
+}
+
+void stop_webserver(httpd_handle_t server)
+{
+    // Stop the httpd server
+    httpd_stop(server);
+}
+
+static esp_err_t event_handler(void *ctx, system_event_t *event)
+{
+    httpd_handle_t *server = (httpd_handle_t *) ctx;
+
+    switch(event->event_id) {
+    case SYSTEM_EVENT_STA_START:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
+        ESP_ERROR_CHECK(esp_wifi_connect());
+        break;
+    case SYSTEM_EVENT_STA_GOT_IP:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
+        ESP_LOGI(TAG, "Got IP: '%s'",
+                 ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
+
+        /* Start the web server */
+        if (*server == NULL) {
+            *server = start_webserver();
+        }
+        break;
+    case SYSTEM_EVENT_STA_DISCONNECTED:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
+        ESP_ERROR_CHECK(esp_wifi_connect());
+
+        /* Stop the webserver */
+        if (*server) {
+            stop_webserver(*server);
+            *server = NULL;
+        }
+        break;
+    default:
+        break;
+    }
+    return ESP_OK;
+}
+
+static void initialise_wifi(void *arg)
+{
+    tcpip_adapter_init();
+    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, arg));
+    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
+    wifi_config_t wifi_config = {
+        .sta = {
+            .ssid = EXAMPLE_WIFI_SSID,
+            .password = EXAMPLE_WIFI_PASS,
+        },
+    };
+    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
+    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
+    ESP_ERROR_CHECK(esp_wifi_start());
+}
+
+void app_main()
+{
+    static httpd_handle_t server = NULL;
+    ESP_ERROR_CHECK(nvs_flash_init());
+    initialise_wifi(&server);
+}
diff --git a/examples/protocols/http_server/persistent_sockets/scripts/adder.py b/examples/protocols/http_server/persistent_sockets/scripts/adder.py
new file mode 100644
index 00000000..55ca7508
--- /dev/null
+++ b/examples/protocols/http_server/persistent_sockets/scripts/adder.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import print_function
+from __future__ import unicode_literals
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import range
+import http.client
+import argparse
+
+def start_session (ip, port):
+    return http.client.HTTPConnection(ip, int(port), timeout=15)
+
+def end_session (conn):
+    conn.close()
+
+def getreq (conn, path, verbose = False):
+    conn.request("GET", path)
+    resp = conn.getresponse()
+    data = resp.read()
+    if verbose:
+        Utility.console_log("GET : " + path)
+        Utility.console_log("Status : " + resp.status)
+        Utility.console_log("Reason : " + resp.reason)
+        Utility.console_log("Data length  : " + str(len(data)))
+        Utility.console_log("Data content : " + data)
+    return data
+
+def postreq (conn, path, data, verbose = False):
+    conn.request("POST", path, data)
+    resp = conn.getresponse()
+    data = resp.read()
+    if verbose:
+        Utility.console_log("POST : " + data)
+        Utility.console_log("Status : " + resp.status)
+        Utility.console_log("Reason : " + resp.reason)
+        Utility.console_log("Data length  : " + str(len(data)))
+        Utility.console_log("Data content : " + data)
+    return data
+
+def putreq (conn, path, body, verbose = False):
+    conn.request("PUT", path, body)
+    resp = conn.getresponse()
+    data = resp.read()
+    if verbose:
+        Utility.console_log("PUT : " + path, body)
+        Utility.console_log("Status : " + resp.status)
+        Utility.console_log("Reason : " + resp.reason)
+        Utility.console_log("Data length  : " + str(len(data)))
+        Utility.console_log("Data content : " + data)
+    return data
+
+if __name__ == '__main__':
+    # Configure argument parser
+    parser = argparse.ArgumentParser(description='Run HTTPd Test')
+    parser.add_argument('IP'  , metavar='IP'  ,    type=str, help='Server IP')
+    parser.add_argument('port', metavar='port',    type=str, help='Server port')
+    parser.add_argument('N'   , metavar='integer', type=int, help='Integer to sum upto')
+    args = vars(parser.parse_args())
+
+    # Get arguments
+    ip   = args['IP']
+    port = args['port']
+    N    = args['N']
+
+    # Establish HTTP connection
+    Utility.console_log("Connecting to => " + ip + ":" + port)
+    conn = start_session (ip, port)
+
+    # Reset adder context to specified value(0)
+    # -- Not needed as new connection will always
+    # -- have zero value of the accumulator
+    Utility.console_log("Reset the accumulator to 0")
+    putreq (conn, "/adder", str(0))
+
+    # Sum numbers from 1 to specified value(N)
+    Utility.console_log("Summing numbers from 1 to " + str(N))
+    for i in range(1, N+1):
+        postreq (conn, "/adder", str(i))
+
+    # Fetch the result
+    Utility.console_log("Result :" + getreq (conn, "/adder"))
+
+    # Close HTTP connection
+    end_session (conn)
diff --git a/examples/protocols/http_server/simple/CMakeLists.txt b/examples/protocols/http_server/simple/CMakeLists.txt
new file mode 100644
index 00000000..cc9d4fd8
--- /dev/null
+++ b/examples/protocols/http_server/simple/CMakeLists.txt
@@ -0,0 +1,6 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(simple)
diff --git a/examples/protocols/http_server/simple/Makefile b/examples/protocols/http_server/simple/Makefile
new file mode 100644
index 00000000..48f628a6
--- /dev/null
+++ b/examples/protocols/http_server/simple/Makefile
@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := simple
+
+include $(IDF_PATH)/make/project.mk
+
diff --git a/examples/protocols/http_server/simple/README.md b/examples/protocols/http_server/simple/README.md
new file mode 100644
index 00000000..b9af67ec
--- /dev/null
+++ b/examples/protocols/http_server/simple/README.md
@@ -0,0 +1,27 @@
+# Simple HTTPD Server Example
+
+The Example consists of HTTPD server demo with demostration of URI handling :
+    1. URI \hello for GET command returns "Hello World!" message
+    2. URI \echo for POST command echoes back the POSTed message
+
+* Configure the project using "make menuconfig" and goto :
+    * Example Configuration ->
+        1. WIFI SSID: WIFI network to which your PC is also connected to.
+        2. WIFI Password: WIFI password
+
+* In order to test the HTTPD server persistent sockets demo :
+    1. compile and burn the firmware "make flash"
+    2. run "make monitor" and note down the IP assigned to your ESP module. The default port is 80
+    3. test the example :
+        * run the test script : "python2 scripts/client.py \<IP\> \<port\> \<MSG\>"
+            * the provided test script first does a GET \hello and displays the response
+            * the script does a POST to \echo with the user input \<MSG\> and displays the response
+        * or use curl (asssuming IP is 192.168.43.130):
+            1. "curl 192.168.43.130:80/hello"  - tests the GET "\hello" handler
+            2. "curl -X POST --data-binary @anyfile 192.168.43.130:80/echo > tmpfile"
+                * "anyfile" is the file being sent as request body and "tmpfile" is where the body of the response is saved
+                * since the server echoes back the request body, the two files should be same, as can be confirmed using : "cmp anyfile tmpfile"
+            3. "curl -X PUT -d "0" 192.168.43.130:80/ctrl" - disable /hello and /echo handlers
+            4. "curl -X PUT -d "1" 192.168.43.130:80/ctrl" -  enable /hello and /echo handlers
+
+See the README.md file in the upper level 'examples' directory for more information about examples.
diff --git a/examples/protocols/http_server/simple/http_server_simple_test.py b/examples/protocols/http_server/simple/http_server_simple_test.py
new file mode 100644
index 00000000..b9b71a53
--- /dev/null
+++ b/examples/protocols/http_server/simple/http_server_simple_test.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+from builtins import range
+import imp
+import re
+import os
+import sys
+import string
+import random
+import socket
+
+# This environment variable is expected on the host machine
+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)
+
+# When running on local machine execute the following before running this script
+# > make app bootloader
+# > make print_flash_cmd | tail -n 1 > build/download.config
+# > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
+
+import TinyFW
+import IDF
+import Utility
+
+# Import client module
+expath = os.path.dirname(os.path.realpath(__file__))
+client = imp.load_source("client", expath + "/scripts/client.py")
+
+@IDF.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_http_server_simple(env, extra_data):
+    # Acquire DUT
+    dut1 = env.get_dut("http_server", "examples/protocols/http_server/simple")
+
+    # Get binary file
+    binary_file = os.path.join(dut1.app.binary_path, "simple.bin")
+    bin_size = os.path.getsize(binary_file)
+    IDF.log_performance("http_server_bin_size", "{}KB".format(bin_size//1024))
+    IDF.check_performance("http_server_bin_size", bin_size//1024)
+
+    # Upload binary and start testing
+    Utility.console_log("Starting http_server simple test app")
+    dut1.start_app()
+
+    # Parse IP address of STA
+    Utility.console_log("Waiting to connect with AP")
+    got_ip   = dut1.expect(re.compile(r"(?:[\s\S]*)Got IP: '(\d+.\d+.\d+.\d+)'"), timeout=120)[0]
+    got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Starting server on port: '(\d+)'"), timeout=30)[0]
+
+    Utility.console_log("Got IP   : " + got_ip)
+    Utility.console_log("Got Port : " + got_port)
+
+    # Expected Logs
+    dut1.expect("Registering URI handlers", timeout=30)
+
+    # Run test script
+    # If failed raise appropriate exception
+    Utility.console_log("Test /hello GET handler")
+    if not client.test_get_handler(got_ip, got_port):
+        raise RuntimeError
+
+    # Acquire host IP. Need a way to check it
+    host_ip = dut1.expect(re.compile(r"(?:[\s\S]*)Found header => Host: (\d+.\d+.\d+.\d+)"), timeout=30)[0]
+
+    # Match additional headers sent in the request
+    dut1.expect("Found header => Test-Header-2: Test-Value-2", timeout=30)
+    dut1.expect("Found header => Test-Header-1: Test-Value-1", timeout=30)
+    dut1.expect("Found URL query parameter => query1=value1", timeout=30)
+    dut1.expect("Found URL query parameter => query3=value3", timeout=30)
+    dut1.expect("Found URL query parameter => query2=value2", timeout=30)
+    dut1.expect("Request headers lost", timeout=30)
+
+    Utility.console_log("Test /ctrl PUT handler and realtime handler de/registration")
+    if not client.test_put_handler(got_ip, got_port):
+        raise RuntimeError
+    dut1.expect("Unregistering /hello and /echo URIs", timeout=30)
+    dut1.expect("Registering /hello and /echo URIs", timeout=30)
+
+    # Generate random data of 10KB
+    random_data = ''.join(string.printable[random.randint(0,len(string.printable))-1] for _ in range(10*1024))
+    Utility.console_log("Test /echo POST handler with random data")
+    if not client.test_post_handler(got_ip, got_port, random_data):
+        raise RuntimeError
+
+    query = "http://foobar"
+    Utility.console_log("Test /hello with custom query : " + query)
+    if not client.test_custom_uri_query(got_ip, got_port, query):
+        raise RuntimeError
+    dut1.expect("Found URL query => " + query, timeout=30)
+
+    query = "abcd+1234%20xyz"
+    Utility.console_log("Test /hello with custom query : " + query)
+    if not client.test_custom_uri_query(got_ip, got_port, query):
+        raise RuntimeError
+    dut1.expect("Found URL query => " + query, timeout=30)
+
+    query = "abcd\nyz"
+    Utility.console_log("Test /hello with invalid query")
+    if client.test_custom_uri_query(got_ip, got_port, query):
+        raise RuntimeError
+    dut1.expect("400 Bad Request - Server unable to understand request due to invalid syntax", timeout=30)
+
+if __name__ == '__main__':
+    test_examples_protocol_http_server_simple()
diff --git a/examples/protocols/http_server/simple/main/CMakeLists.txt b/examples/protocols/http_server/simple/main/CMakeLists.txt
new file mode 100644
index 00000000..85970762
--- /dev/null
+++ b/examples/protocols/http_server/simple/main/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(COMPONENT_SRCS "main.c")
+set(COMPONENT_ADD_INCLUDEDIRS ".")
+
+register_component()
diff --git a/examples/protocols/http_server/simple/main/Kconfig.projbuild b/examples/protocols/http_server/simple/main/Kconfig.projbuild
new file mode 100644
index 00000000..9e2813c6
--- /dev/null
+++ b/examples/protocols/http_server/simple/main/Kconfig.projbuild
@@ -0,0 +1,16 @@
+menu "Example Configuration"
+
+config WIFI_SSID
+    string "WiFi SSID"
+    default "myssid"
+    help
+        SSID (network name) for the example to connect to.
+
+config WIFI_PASSWORD
+    string "WiFi Password"
+    default "mypassword"
+    help
+        WiFi password (WPA or WPA2) for the example to use.
+        Can be left blank if the network has no security set.
+
+endmenu
diff --git a/examples/protocols/http_server/simple/main/component.mk b/examples/protocols/http_server/simple/main/component.mk
new file mode 100644
index 00000000..0b9d7585
--- /dev/null
+++ b/examples/protocols/http_server/simple/main/component.mk
@@ -0,0 +1,5 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
diff --git a/examples/protocols/http_server/simple/main/main.c b/examples/protocols/http_server/simple/main/main.c
new file mode 100644
index 00000000..d4b7b386
--- /dev/null
+++ b/examples/protocols/http_server/simple/main/main.c
@@ -0,0 +1,279 @@
+/* Simple HTTP Server Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <esp_wifi.h>
+#include <esp_event_loop.h>
+#include <esp_log.h>
+#include <esp_system.h>
+#include <nvs_flash.h>
+#include <sys/param.h>
+
+#include <esp_http_server.h>
+
+/* A simple example that demonstrates how to create GET and POST
+ * handlers for the web server.
+ * The examples use simple WiFi configuration that you can set via
+ * 'make menuconfig'.
+ * If you'd rather not, just change the below entries to strings
+ * with the config you want -
+ * ie. #define EXAMPLE_WIFI_SSID "mywifissid"
+*/
+#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
+#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
+
+static const char *TAG="APP";
+
+/* An HTTP GET handler */
+esp_err_t hello_get_handler(httpd_req_t *req)
+{
+    char*  buf;
+    size_t buf_len;
+
+    /* Get header value string length and allocate memory for length + 1,
+     * extra byte for null termination */
+    buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
+    if (buf_len > 1) {
+        buf = malloc(buf_len);
+        /* Copy null terminated value string into buffer */
+        if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) {
+            ESP_LOGI(TAG, "Found header => Host: %s", buf);
+        }
+        free(buf);
+    }
+
+    buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-2") + 1;
+    if (buf_len > 1) {
+        buf = malloc(buf_len);
+        if (httpd_req_get_hdr_value_str(req, "Test-Header-2", buf, buf_len) == ESP_OK) {
+            ESP_LOGI(TAG, "Found header => Test-Header-2: %s", buf);
+        }
+        free(buf);
+    }
+
+    buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-1") + 1;
+    if (buf_len > 1) {
+        buf = malloc(buf_len);
+        if (httpd_req_get_hdr_value_str(req, "Test-Header-1", buf, buf_len) == ESP_OK) {
+            ESP_LOGI(TAG, "Found header => Test-Header-1: %s", buf);
+        }
+        free(buf);
+    }
+
+    /* Read URL query string length and allocate memory for length + 1,
+     * extra byte for null termination */
+    buf_len = httpd_req_get_url_query_len(req) + 1;
+    if (buf_len > 1) {
+        buf = malloc(buf_len);
+        if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
+            ESP_LOGI(TAG, "Found URL query => %s", buf);
+            char param[32];
+            /* Get value of expected key from query string */
+            if (httpd_query_key_value(buf, "query1", param, sizeof(param)) == ESP_OK) {
+                ESP_LOGI(TAG, "Found URL query parameter => query1=%s", param);
+            }
+            if (httpd_query_key_value(buf, "query3", param, sizeof(param)) == ESP_OK) {
+                ESP_LOGI(TAG, "Found URL query parameter => query3=%s", param);
+            }
+            if (httpd_query_key_value(buf, "query2", param, sizeof(param)) == ESP_OK) {
+                ESP_LOGI(TAG, "Found URL query parameter => query2=%s", param);
+            }
+        }
+        free(buf);
+    }
+
+    /* Set some custom headers */
+    httpd_resp_set_hdr(req, "Custom-Header-1", "Custom-Value-1");
+    httpd_resp_set_hdr(req, "Custom-Header-2", "Custom-Value-2");
+
+    /* Send response with custom headers and body set as the
+     * string passed in user context*/
+    const char* resp_str = (const char*) req->user_ctx;
+    httpd_resp_send(req, resp_str, strlen(resp_str));
+
+    /* After sending the HTTP response the old HTTP request
+     * headers are lost. Check if HTTP request headers can be read now. */
+    if (httpd_req_get_hdr_value_len(req, "Host") == 0) {
+        ESP_LOGI(TAG, "Request headers lost");
+    }
+    return ESP_OK;
+}
+
+httpd_uri_t hello = {
+    .uri       = "/hello",
+    .method    = HTTP_GET,
+    .handler   = hello_get_handler,
+    /* Let's pass response string in user
+     * context to demonstrate it's usage */
+    .user_ctx  = "Hello World!"
+};
+
+/* An HTTP POST handler */
+esp_err_t echo_post_handler(httpd_req_t *req)
+{
+    char buf[100];
+    int ret, remaining = req->content_len;
+
+    while (remaining > 0) {
+        /* Read the data for the request */
+        if ((ret = httpd_req_recv(req, buf,
+                        MIN(remaining, sizeof(buf)))) <= 0) {
+            if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
+                /* Retry receiving if timeout occurred */
+                continue;
+            }
+            return ESP_FAIL;
+        }
+
+        /* Send back the same data */
+        httpd_resp_send_chunk(req, buf, ret);
+        remaining -= ret;
+
+        /* Log data received */
+        ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
+        ESP_LOGI(TAG, "%.*s", ret, buf);
+        ESP_LOGI(TAG, "====================================");
+    }
+
+    // End response
+    httpd_resp_send_chunk(req, NULL, 0);
+    return ESP_OK;
+}
+
+httpd_uri_t echo = {
+    .uri       = "/echo",
+    .method    = HTTP_POST,
+    .handler   = echo_post_handler,
+    .user_ctx  = NULL
+};
+
+/* An HTTP PUT handler. This demonstrates realtime
+ * registration and deregistration of URI handlers
+ */
+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_SOCK_ERR_TIMEOUT) {
+            httpd_resp_send_408(req);
+        }
+        return ESP_FAIL;
+    }
+
+    if (buf == '0') {
+        /* Handler 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");
+    }
+    else {
+        ESP_LOGI(TAG, "Registering /hello and /echo URIs");
+        httpd_register_uri_handler(req->handle, &hello);
+        httpd_register_uri_handler(req->handle, &echo);
+    }
+
+    /* Respond with empty body */
+    httpd_resp_send(req, NULL, 0);
+    return ESP_OK;
+}
+
+httpd_uri_t ctrl = {
+    .uri       = "/ctrl",
+    .method    = HTTP_PUT,
+    .handler   = ctrl_put_handler,
+    .user_ctx  = NULL
+};
+
+httpd_handle_t start_webserver(void)
+{
+    httpd_handle_t server = NULL;
+    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+
+    // Start the httpd server
+    ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
+    if (httpd_start(&server, &config) == ESP_OK) {
+        // Set URI handlers
+        ESP_LOGI(TAG, "Registering URI handlers");
+        httpd_register_uri_handler(server, &hello);
+        httpd_register_uri_handler(server, &echo);
+        httpd_register_uri_handler(server, &ctrl);
+        return server;
+    }
+
+    ESP_LOGI(TAG, "Error starting server!");
+    return NULL;
+}
+
+void stop_webserver(httpd_handle_t server)
+{
+    // Stop the httpd server
+    httpd_stop(server);
+}
+
+static esp_err_t event_handler(void *ctx, system_event_t *event)
+{
+    httpd_handle_t *server = (httpd_handle_t *) ctx;
+
+    switch(event->event_id) {
+    case SYSTEM_EVENT_STA_START:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
+        ESP_ERROR_CHECK(esp_wifi_connect());
+        break;
+    case SYSTEM_EVENT_STA_GOT_IP:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
+        ESP_LOGI(TAG, "Got IP: '%s'",
+                ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
+
+        /* Start the web server */
+        if (*server == NULL) {
+            *server = start_webserver();
+        }
+        break;
+    case SYSTEM_EVENT_STA_DISCONNECTED:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
+        ESP_ERROR_CHECK(esp_wifi_connect());
+
+        /* Stop the web server */
+        if (*server) {
+            stop_webserver(*server);
+            *server = NULL;
+        }
+        break;
+    default:
+        break;
+    }
+    return ESP_OK;
+}
+
+static void initialise_wifi(void *arg)
+{
+    tcpip_adapter_init();
+    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, arg));
+    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
+    wifi_config_t wifi_config = {
+        .sta = {
+            .ssid = EXAMPLE_WIFI_SSID,
+            .password = EXAMPLE_WIFI_PASS,
+        },
+    };
+    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
+    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
+    ESP_ERROR_CHECK(esp_wifi_start());
+}
+
+void app_main()
+{
+    static httpd_handle_t server = NULL;
+    ESP_ERROR_CHECK(nvs_flash_init());
+    initialise_wifi(&server);
+}
diff --git a/examples/protocols/http_server/simple/scripts/client.py b/examples/protocols/http_server/simple/scripts/client.py
new file mode 100644
index 00000000..21220b54
--- /dev/null
+++ b/examples/protocols/http_server/simple/scripts/client.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import print_function
+from __future__ import unicode_literals
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+import http.client
+import argparse
+
+def verbose_print(verbosity, *args):
+    if (verbosity):
+        Utility.console_log(''.join(str(elems) for elems in args))
+
+def test_get_handler(ip, port, verbosity = False):
+    verbose_print(verbosity, "========  GET HANDLER TEST =============")
+    # Establish HTTP connection
+    verbose_print(verbosity, "Connecting to => " + ip + ":" + port)
+    sess = http.client.HTTPConnection(ip + ":" + port, timeout = 15)
+
+    uri = "/hello?query1=value1&query2=value2&query3=value3"
+    # GET hello response
+    test_headers = {"Test-Header-1":"Test-Value-1", "Test-Header-2":"Test-Value-2"}
+    verbose_print(verbosity, "Sending GET to URI : ", uri)
+    verbose_print(verbosity, "Sending additional headers : ")
+    for k, v in test_headers.items():
+        verbose_print(verbosity, "\t", k, ": ", v)
+    sess.request("GET", url=uri, headers=test_headers)
+    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:
+        return False
+
+    verbose_print(verbosity, "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv")
+    verbose_print(verbosity, "Server response to GET /hello")
+    verbose_print(verbosity, "Response Headers : ")
+    for k, v in resp_hdrs:
+        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!")
+
+def test_post_handler(ip, port, msg, verbosity = False):
+    verbose_print(verbosity, "========  POST HANDLER TEST ============")
+    # Establish HTTP connection
+    verbose_print(verbosity, "Connecting to => " + ip + ":" + port)
+    sess = http.client.HTTPConnection(ip + ":" + port, timeout = 15)
+
+    # POST message to /echo and get back response
+    sess.request("POST", url="/echo", body=msg)
+    resp = sess.getresponse()
+    resp_data = resp.read().decode()
+    verbose_print(verbosity, "Server response to POST /echo (" + msg + ")")
+    verbose_print(verbosity, "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv")
+    verbose_print(verbosity, resp_data)
+    verbose_print(verbosity, "========================================\n")
+
+    # Close HTTP connection
+    sess.close()
+    return (resp_data == msg)
+
+def test_put_handler(ip, port, verbosity = False):
+    verbose_print(verbosity, "========  PUT HANDLER TEST =============")
+    # Establish HTTP connection
+    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")
+    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)
+
+    # 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()
+
+    sess.request("GET", url="/hello")
+    resp = sess.getresponse()
+    resp_data2 = resp.read().decode()
+    verbose_print(verbosity, "Response on GET /hello : " + resp_data2)
+
+    # Close HTTP connection
+    sess.close()
+    return ((resp_data2 == "Hello World!") and (resp_data1 == "This URI doesn't exist"))
+
+def test_custom_uri_query(ip, port, query, verbosity = False):
+    verbose_print(verbosity, "========  GET HANDLER TEST =============")
+    # Establish HTTP connection
+    verbose_print(verbosity, "Connecting to => " + ip + ":" + port)
+    sess = http.client.HTTPConnection(ip + ":" + port, timeout = 15)
+
+    uri = "/hello?" + query
+    # GET hello response
+    verbose_print(verbosity, "Sending GET to URI : ", uri)
+    sess.request("GET", url=uri, headers={})
+    resp = sess.getresponse()
+    resp_data = resp.read().decode()
+
+    verbose_print(verbosity, "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv")
+    verbose_print(verbosity, "Server response to GET /hello")
+    verbose_print(verbosity, "Response Data : " + resp_data)
+    verbose_print(verbosity, "========================================\n")
+
+    # Close HTTP connection
+    sess.close()
+    return (resp_data == "Hello World!")
+
+if __name__ == '__main__':
+    # Configure argument parser
+    parser = argparse.ArgumentParser(description='Run HTTPd Test')
+    parser.add_argument('IP'  , metavar='IP'  ,    type=str, help='Server IP')
+    parser.add_argument('port', metavar='port',    type=str, help='Server port')
+    parser.add_argument('msg',  metavar='message', type=str, help='Message to be sent to server')
+    args = vars(parser.parse_args())
+
+    # Get arguments
+    ip   = args['IP']
+    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):
+        Utility.console_log("Failed!")