.global_transport_ctx_free_fn = NULL, \
.open_fn = NULL, \
.close_fn = NULL, \
+ .uri_match_fn = NULL \
}
#define ESP_ERR_HTTPD_BASE (0x8000) /*!< Starting number of HTTPD error codes */
#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 */
+/* Symbol to be used as length parameter in httpd_resp_send APIs
+ * for setting buffer length to string length */
+#define HTTPD_RESP_USE_STRLEN -1
+
/* ************** Group: Initialization ************** */
/** @name Initialization
* APIs related to the Initialization of the web server
/**
* @brief Prototype for freeing context data (if any)
- * @param[in] ctx : object to free
+ * @param[in] ctx object to free
*/
typedef void (*httpd_free_ctx_fn_t)(void *ctx);
* 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
+ * @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);
* @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
+ * @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 Function prototype for URI matching.
+ *
+ * @param[in] reference_uri URI/template with respect to which the other URI is matched
+ * @param[in] uri_to_match URI/template being matched to the reference URI/template
+ * @param[in] match_upto For specifying the actual length of `uri_to_match` up to
+ * which the matching algorithm is to be applied (The maximum
+ * value is `strlen(uri_to_match)`, independent of the length
+ * of `reference_uri`)
+ * @return true on match
+ */
+typedef bool (*httpd_uri_match_func_t)(const char *reference_uri,
+ const char *uri_to_match,
+ size_t match_upto);
+
/**
* @brief HTTP Server Configuration Structure
*
* was closed by the network stack - that is, the file descriptor may not be valid anymore.
*/
httpd_close_func_t close_fn;
+
+ /**
+ * URI matcher function.
+ *
+ * Called when searching for a matching URI:
+ * 1) whose request handler is to be executed right
+ * after an HTTP request is successfully parsed
+ * 2) in order to prevent duplication while registering
+ * a new URI handler using `httpd_register_uri_handler()`
+ *
+ * Available options are:
+ * 1) NULL : Internally do basic matching using `strncmp()`
+ * 2) `httpd_uri_match_wildcard()` : URI wildcard matcher
+ *
+ * Users can implement their own matching functions (See description
+ * of the `httpd_uri_match_func_t` function prototype)
+ */
+ httpd_uri_match_func_t uri_match_fn;
} httpd_config_t;
/**
*
* @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
+ * @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)
* 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
+ * @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_ 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
+ * @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_ codes, which will be handled accordingly in
* the server task.
*
- * @param[in] hd : server instance
- * @param[in] sockfd : session socket file descriptor
+ * @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
*/
esp_err_t httpd_query_key_value(const char *qry, const char *key, char *val, size_t val_size);
-/* Symbol to be used as length parameter in httpd_resp_send APIs
- * for setting buffer length to string length */
-#define HTTPD_RESP_USE_STRLEN -1
+/**
+ * @brief Test if a URI matches the given wildcard template.
+ *
+ * Template may end with "?" to make the previous character optional (typically a slash),
+ * "*" for a wildcard match, and "?*" to make the previous character optional, and if present,
+ * allow anything to follow.
+ *
+ * Example:
+ * - * matches everything
+ * - /foo/? matches /foo and /foo/
+ * - /foo/\* (sans the backslash) matches /foo/ and /foo/bar, but not /foo or /fo
+ * - /foo/?* or /foo/\*? (sans the backslash) matches /foo/, /foo/bar, and also /foo, but not /foox or /fo
+ *
+ * The special characters "?" and "*" anywhere else in the template will be taken literally.
+ *
+ * @param[in] template URI template (pattern)
+ * @param[in] uri URI to be matched
+ * @param[in] len how many characters of the URI buffer to test
+ * (there may be trailing query string etc.)
+ *
+ * @return true if a match was found
+ */
+bool httpd_uri_match_wildcard(const char *template, const char *uri, size_t len);
/**
* @brief API to send a complete HTTP response.
static const char *TAG = "httpd_uri";
+static bool httpd_uri_match_simple(const char *uri1, const char *uri2, size_t len2)
+{
+ return strlen(uri1) == len2 && // First match lengths
+ (strncmp(uri1, uri2, len2) == 0); // Then match actual URIs
+}
-/**
- * @brief Test if a URI matches the given template.
- *
- * Template may end with "?" to make the previous character optional (typically a slash),
- * "*" for a wildcard match, and "?*" to make the previous character optional, and if present,
- * allow anything to follow.
- *
- * Example:
- * - * matches everything
- * - /foo/? matches /foo and /foo/
- * - /foo/\* (sans the backslash) matches /foo/ and /foo/bar, but not /foo or /fo
- * - /foo/?* or /foo/\*? (sans the backslash) matches /foo/, /foo/bar, and also /foo, but not /foox or /fo
- *
- * The special characters "?" and "*" anywhere else in the template will be taken literally.
- *
- * @param[in] template - URI template (pattern)
- * @param[in] uri - tested URI
- * @param[in] template - how many characters of the URI buffer to test
- * (there may be trailing query string etc.)
- *
- * @return true if a match was found
- */
-static bool uri_matches(const char *template, const char *uri, const unsigned int len)
+bool httpd_uri_match_wildcard(const char *template, const char *uri, size_t len)
{
const size_t tpl_len = strlen(template);
size_t exact_match_chars = tpl_len;
const bool asterisk = last == '*' || (prevlast == '*' && last == '?');
const bool quest = last == '?' || (prevlast == '?' && last == '*');
+ /* Minimum template string length must be:
+ * 0 : if neither of '*' and '?' are present
+ * 1 : if only '*' is present
+ * 2 : if only '?' is present
+ * 3 : if both are present
+ *
+ * The expression (asterisk + quest*2) serves as a
+ * case wise generator of these length values
+ */
+
/* abort in cases such as "?" with no preceding character (invalid template) */
- if (exact_match_chars < asterisk + quest*2) return false;
+ if (exact_match_chars < asterisk + quest*2) {
+ return false;
+ }
+
/* account for special characters and the optional character if "?" is used */
exact_match_chars -= asterisk + quest*2;
- if (len < exact_match_chars) return false;
+ if (len < exact_match_chars) {
+ return false;
+ }
if (!quest) {
if (!asterisk && len != exact_match_chars) {
}
/* asterisk allows arbitrary trailing characters, we ignore these using
* exact_match_chars as the length limit */
- return 0 == strncmp(template, uri, exact_match_chars);
+ return (strncmp(template, uri, exact_match_chars) == 0);
} else {
/* question mark present */
if (len > exact_match_chars && template[exact_match_chars] != uri[exact_match_chars]) {
/* the optional character is present, but different */
return false;
}
- if (0 != strncmp(template, uri, exact_match_chars)) {
+ if (strncmp(template, uri, exact_match_chars) != 0) {
/* the mandatory part differs */
return false;
}
}
}
-static int httpd_find_uri_handler(struct httpd_data *hd,
- const char* uri,
- httpd_method_t method)
+/* Find handler with matching URI and method, and set
+ * appropriate error code if URI or method not found */
+static httpd_uri_t* httpd_find_uri_handler(struct httpd_data *hd,
+ const char *uri, size_t uri_len,
+ httpd_method_t method,
+ httpd_err_resp_t *err)
{
+ if (err) {
+ *err = HTTPD_404_NOT_FOUND;
+ }
+
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
- uri_matches(hd->hd_calls[i]->uri, uri, strlen(uri))) { // Then match uri strings
- return i;
+ if (!hd->hd_calls[i]) {
+ break;
+ }
+ ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri);
+
+ /* Check if custom URI matching function is set,
+ * else use simple string compare */
+ if (hd->config.uri_match_fn ?
+ hd->config.uri_match_fn(hd->hd_calls[i]->uri, uri, uri_len) :
+ httpd_uri_match_simple(hd->hd_calls[i]->uri, uri, uri_len)) {
+ /* URIs match. Now check if method is supported */
+ if (hd->hd_calls[i]->method == method) {
+ /* Match found! */
+ if (err) {
+ /* Unset any error that may
+ * have been set earlier */
+ *err = 0;
+ }
+ return hd->hd_calls[i];
+ }
+ /* URI found but method not allowed.
+ * If URI is found later then this
+ * error must be set to 0 */
+ if (err) {
+ *err = HTTPD_405_METHOD_NOT_ALLOWED;
}
}
}
- return -1;
+ return NULL;
}
esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
struct httpd_data *hd = (struct httpd_data *) handle;
- /* Make sure another handler with same URI and method
- * is not already registered
- */
+ /* Make sure another handler with matching URI and method
+ * is not already registered. This will also catch cases
+ * when a registered URI wildcard pattern already accounts
+ * for the new URI being registered */
if (httpd_find_uri_handler(handle, uri_handler->uri,
- uri_handler->method) != -1) {
+ strlen(uri_handler->uri),
+ uri_handler->method, NULL) != NULL) {
ESP_LOGW(TAG, LOG_FMT("handler %s with method %d already registered"),
uri_handler->uri, uri_handler->method);
return ESP_ERR_HTTPD_HANDLER_EXISTS;
}
struct httpd_data *hd = (struct httpd_data *) handle;
- int i = httpd_find_uri_handler(hd, uri, method);
+ for (int i = 0; i < hd->config.max_uri_handlers; i++) {
+ if (!hd->hd_calls[i]) {
+ break;
+ }
+ if ((hd->hd_calls[i]->method == method) && // First match methods
+ (strcmp(hd->hd_calls[i]->uri, uri) == 0)) { // Then match URI string
+ ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
- 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;
- free((char*)hd->hd_calls[i]->uri);
- free(hd->hd_calls[i]);
- hd->hd_calls[i] = NULL;
- return ESP_OK;
+ /* Shift the remaining non null handlers in the array
+ * forward by 1 so that order of insertion is maintained */
+ for (i += 1; i < hd->config.max_uri_handlers; i++) {
+ if (!hd->hd_calls[i]) {
+ break;
+ }
+ hd->hd_calls[i-1] = hd->hd_calls[i];
+ }
+ /* Nullify the following non null entry */
+ hd->hd_calls[i-1] = NULL;
+ return ESP_OK;
+ }
}
ESP_LOGW(TAG, LOG_FMT("handler %s with method %d not found"), uri, method);
return ESP_ERR_NOT_FOUND;
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)) {
+ int i = 0, j = 0; // For keeping count of removed entries
+ for (; i < hd->config.max_uri_handlers; i++) {
+ if (!hd->hd_calls[i]) {
+ break;
+ }
+ if (strcmp(hd->hd_calls[i]->uri, uri) == 0) { // Match URI strings
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;
+
+ j++; // Update count of removed entries
+ } else {
+ /* Shift the remaining non null handlers in the array
+ * forward by j so that order of insertion is maintained */
+ hd->hd_calls[i-j] = hd->hd_calls[i];
}
}
+ /* Nullify the following non null entries */
+ for (int k = (i - j); k < i; k++) {
+ hd->hd_calls[k] = NULL;
+ }
+
if (!found) {
ESP_LOGW(TAG, LOG_FMT("no handler found for URI %s"), uri);
}
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]);
+ if (!hd->hd_calls[i]) {
+ break;
}
- }
-}
+ ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
-/* 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 (uri_matches(hd->hd_calls[i]->uri, uri, uri_len)) {
- if (hd->hd_calls[i]->method == method) { // 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;
+ free((char*)hd->hd_calls[i]->uri);
+ free(hd->hd_calls[i]);
+ hd->hd_calls[i] = NULL;
}
- return NULL;
}
esp_err_t httpd_uri(struct httpd_data *hd)
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);
+ uri = httpd_find_uri_handler(hd, req->uri + res->field_data[UF_PATH].off,
+ res->field_data[UF_PATH].len, req->method, &err);
}
/* If URI with method not found, respond with error code */
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);
+ 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;