| 2 |
raymond |
1 |
# AsyncURIMatcher Example
|
|
|
2 |
|
|
|
3 |
This example demonstrates the comprehensive URI matching capabilities of the ESPAsyncWebServer library using the `AsyncURIMatcher` class.
|
|
|
4 |
|
|
|
5 |
## Overview
|
|
|
6 |
|
|
|
7 |
The `AsyncURIMatcher` class provides flexible and powerful URL routing mechanisms that go beyond simple string matching. It supports various matching strategies that can be combined to create sophisticated routing rules.
|
|
|
8 |
|
|
|
9 |
**Important**: When using plain strings (not `AsyncURIMatcher` objects), the library uses auto-detection (`URIMatchAuto`) which analyzes the URI pattern and applies appropriate matching rules. This is **not** simple exact matching - it combines exact and folder matching by default!
|
|
|
10 |
|
|
|
11 |
## What's Demonstrated
|
|
|
12 |
|
|
|
13 |
This example includes two Arduino sketches:
|
|
|
14 |
|
|
|
15 |
1. **URIMatcher.ino** - Interactive web-based demonstration with a user-friendly homepage
|
|
|
16 |
2. **URIMatcherTest.ino** - Comprehensive test suite with automated shell script testing
|
|
|
17 |
|
|
|
18 |
Both sketches create a WiFi Access Point (`esp-captive`) for easy testing without network configuration.
|
|
|
19 |
|
|
|
20 |
## Auto-Detection Behavior
|
|
|
21 |
|
|
|
22 |
When you pass a plain string or `const char*` to `server.on()`, the `URIMatchAuto` flag is used, which:
|
|
|
23 |
|
|
|
24 |
1. **Empty URI**: Matches everything
|
|
|
25 |
2. **Ends with `*`**: Becomes prefix match (`URIMatchPrefix`)
|
|
|
26 |
3. **Contains `/*.ext`**: Becomes extension match (`URIMatchExtension`)
|
|
|
27 |
4. **Starts with `^` and ends with `$`**: Becomes regex match (if enabled)
|
|
|
28 |
5. **Everything else**: Becomes **both** exact and folder match (`URIMatchPrefixFolder | URIMatchExact`)
|
|
|
29 |
|
|
|
30 |
This means traditional string-based routes like `server.on("/path", handler)` will match:
|
|
|
31 |
|
|
|
32 |
- `/path` (exact match)
|
|
|
33 |
- `/path/` (folder with trailing slash)
|
|
|
34 |
- `/path/anything` (folder match)
|
|
|
35 |
|
|
|
36 |
But will **NOT** match `/path-suffix` (prefix without folder separator).
|
|
|
37 |
|
|
|
38 |
## Features Demonstrated
|
|
|
39 |
|
|
|
40 |
### 1. **Auto-Detection (Traditional Behavior)**
|
|
|
41 |
|
|
|
42 |
Demonstrates how traditional string-based routing automatically combines exact and folder matching.
|
|
|
43 |
|
|
|
44 |
**Examples in URIMatcher.ino:**
|
|
|
45 |
|
|
|
46 |
- `/auto` - Matches both `/auto` exactly AND `/auto/sub` as folder
|
|
|
47 |
- `/wildcard*` - Auto-detects as prefix match (due to trailing `*`)
|
|
|
48 |
- `/auto-images/*.png` - Auto-detects as extension match (due to `/*.ext` pattern)
|
|
|
49 |
|
|
|
50 |
**Examples in URIMatcherTest.ino:**
|
|
|
51 |
|
|
|
52 |
- `/exact` - Matches `/exact`, `/exact/`, and `/exact/sub`
|
|
|
53 |
- `/api/users` - Matches exact path and subpaths under `/api/users/`
|
|
|
54 |
- `/*.json` - Matches any `.json` file anywhere
|
|
|
55 |
- `/*.css` - Matches any `.css` file anywhere
|
|
|
56 |
|
|
|
57 |
### 2. **Exact Matching (Factory Method)**
|
|
|
58 |
|
|
|
59 |
Using `AsyncURIMatcher::exact()` matches only the exact URL, **NOT** subpaths.
|
|
|
60 |
|
|
|
61 |
**Key difference from auto-detection:** `AsyncURIMatcher::exact("/path")` matches **only** `/path`, while `server.on("/path", ...)` matches both `/path` and `/path/sub`.
|
|
|
62 |
|
|
|
63 |
**Examples in URIMatcher.ino:**
|
|
|
64 |
|
|
|
65 |
- `AsyncURIMatcher::exact("/exact")` - Matches only `/exact`
|
|
|
66 |
|
|
|
67 |
**Examples in URIMatcherTest.ino:**
|
|
|
68 |
|
|
|
69 |
- `AsyncURIMatcher::exact("/factory/exact")` - Matches only `/factory/exact`
|
|
|
70 |
- Does NOT match `/factory/exact/sub` (404 response)
|
|
|
71 |
|
|
|
72 |
### 3. **Prefix Matching**
|
|
|
73 |
|
|
|
74 |
Using `AsyncURIMatcher::prefix()` matches URLs that start with the specified pattern.
|
|
|
75 |
|
|
|
76 |
**Examples in URIMatcher.ino:**
|
|
|
77 |
|
|
|
78 |
- `AsyncURIMatcher::prefix("/service")` - Matches `/service`, `/service-test`, `/service/status`
|
|
|
79 |
|
|
|
80 |
**Examples in URIMatcherTest.ino:**
|
|
|
81 |
|
|
|
82 |
- `AsyncURIMatcher::prefix("/factory/prefix")` - Matches `/factory/prefix`, `/factory/prefix-test`, `/factory/prefix/sub`
|
|
|
83 |
- Traditional: `/api/*` - Matches `/api/data`, `/api/v1/posts`
|
|
|
84 |
- Traditional: `/files/*` - Matches `/files/document.pdf`, `/files/images/photo.jpg`
|
|
|
85 |
|
|
|
86 |
### 4. **Folder/Directory Matching**
|
|
|
87 |
|
|
|
88 |
Using `AsyncURIMatcher::dir()` matches URLs under a directory (automatically adds trailing slash).
|
|
|
89 |
|
|
|
90 |
**Important:** Directory matching requires a trailing slash in the URL - it does NOT match the directory itself.
|
|
|
91 |
|
|
|
92 |
**Examples in URIMatcher.ino:**
|
|
|
93 |
|
|
|
94 |
- `AsyncURIMatcher::dir("/admin")` - Matches `/admin/users`, `/admin/settings`
|
|
|
95 |
- Does NOT match `/admin` without trailing slash
|
|
|
96 |
|
|
|
97 |
**Examples in URIMatcherTest.ino:**
|
|
|
98 |
|
|
|
99 |
- `AsyncURIMatcher::dir("/factory/dir")` - Matches `/factory/dir/users`, `/factory/dir/sub/path`
|
|
|
100 |
- Does NOT match `/factory/dir` itself (404 response)
|
|
|
101 |
|
|
|
102 |
### 5. **Extension Matching**
|
|
|
103 |
|
|
|
104 |
Using `AsyncURIMatcher::ext()` matches files with specific extensions.
|
|
|
105 |
|
|
|
106 |
**Examples in URIMatcher.ino:**
|
|
|
107 |
|
|
|
108 |
- `AsyncURIMatcher::ext("/images/*.jpg")` - Matches `/images/photo.jpg`, `/images/sub/pic.jpg`
|
|
|
109 |
|
|
|
110 |
**Examples in URIMatcherTest.ino:**
|
|
|
111 |
|
|
|
112 |
- `AsyncURIMatcher::ext("/factory/files/*.txt")` - Matches `/factory/files/doc.txt`, `/factory/files/sub/readme.txt`
|
|
|
113 |
- Does NOT match `/factory/files/doc.pdf` (wrong extension)
|
|
|
114 |
|
|
|
115 |
### 6. **Case Insensitive Matching**
|
|
|
116 |
|
|
|
117 |
Using `AsyncURIMatcher::CaseInsensitive` flag matches URLs regardless of character case.
|
|
|
118 |
|
|
|
119 |
**Examples in URIMatcher.ino:**
|
|
|
120 |
|
|
|
121 |
- `AsyncURIMatcher::exact("/case", AsyncURIMatcher::CaseInsensitive)` - Matches `/case`, `/CASE`, `/CaSe`
|
|
|
122 |
|
|
|
123 |
**Examples in URIMatcherTest.ino:**
|
|
|
124 |
|
|
|
125 |
- Case insensitive exact: `/case/exact`, `/CASE/EXACT`, `/Case/Exact` all work
|
|
|
126 |
- Case insensitive prefix: `/case/prefix`, `/CASE/PREFIX-test`, `/Case/Prefix/sub` all work
|
|
|
127 |
- Case insensitive directory: `/case/dir/users`, `/CASE/DIR/admin`, `/Case/Dir/settings` all work
|
|
|
128 |
- Case insensitive extension: `/case/files/doc.pdf`, `/CASE/FILES/DOC.PDF`, `/Case/Files/Doc.Pdf` all work
|
|
|
129 |
|
|
|
130 |
### 7. **Regular Expression Matching**
|
|
|
131 |
|
|
|
132 |
Using `AsyncURIMatcher::regex()` for advanced pattern matching (requires `ASYNCWEBSERVER_REGEX`).
|
|
|
133 |
|
|
|
134 |
**Examples in URIMatcher.ino:**
|
|
|
135 |
|
|
|
136 |
```cpp
|
|
|
137 |
#ifdef ASYNCWEBSERVER_REGEX
|
|
|
138 |
AsyncURIMatcher::regex("^/user/([0-9]+)$") // Matches /user/123, captures ID
|
|
|
139 |
#endif
|
|
|
140 |
```
|
|
|
141 |
|
|
|
142 |
**Examples in URIMatcherTest.ino:**
|
|
|
143 |
|
|
|
144 |
- Traditional regex: `^/user/([0-9]+)$` - Matches `/user/123`, `/user/456`
|
|
|
145 |
- Traditional regex: `^/blog/([0-9]{4})/([0-9]{2})/([0-9]{2})$` - Matches `/blog/2023/10/15`
|
|
|
146 |
- Factory regex: `AsyncURIMatcher::regex("^/factory/user/([0-9]+)$")` - Matches `/factory/user/123`
|
|
|
147 |
- Factory regex with multiple captures: `^/factory/blog/([0-9]{4})/([0-9]{2})/([0-9]{2})$`
|
|
|
148 |
- Case insensitive regex: `AsyncURIMatcher::regex("^/factory/search/(.+)$", AsyncURIMatcher::CaseInsensitive)`
|
|
|
149 |
|
|
|
150 |
### 8. **Combined Flags**
|
|
|
151 |
|
|
|
152 |
Multiple matching strategies can be combined using the `|` operator.
|
|
|
153 |
|
|
|
154 |
**Examples in URIMatcher.ino:**
|
|
|
155 |
|
|
|
156 |
- `AsyncURIMatcher::prefix("/MixedCase", AsyncURIMatcher::CaseInsensitive)` - Prefix match that's case insensitive
|
|
|
157 |
|
|
|
158 |
### 9. **Special Matchers**
|
|
|
159 |
|
|
|
160 |
**Examples in URIMatcherTest.ino:**
|
|
|
161 |
|
|
|
162 |
- `AsyncURIMatcher::all()` - Matches all requests (used with POST method as catch-all)
|
|
|
163 |
|
|
|
164 |
## Usage Patterns
|
|
|
165 |
|
|
|
166 |
### Traditional String-based Routing (Auto-Detection)
|
|
|
167 |
|
|
|
168 |
```cpp
|
|
|
169 |
// Auto-detection with exact + folder matching
|
|
|
170 |
server.on("/api", handler); // Matches /api AND /api/anything
|
|
|
171 |
server.on("/login", handler); // Matches /login AND /login/sub
|
|
|
172 |
|
|
|
173 |
// Auto-detection with prefix matching
|
|
|
174 |
server.on("/prefix*", handler); // Matches /prefix, /prefix-test, /prefix/sub
|
|
|
175 |
|
|
|
176 |
// Auto-detection with extension matching
|
|
|
177 |
server.on("/images/*.jpg", handler); // Matches /images/pic.jpg, /images/sub/pic.jpg
|
|
|
178 |
```
|
|
|
179 |
|
|
|
180 |
### Explicit AsyncURIMatcher Syntax
|
|
|
181 |
|
|
|
182 |
### Explicit AsyncURIMatcher Syntax
|
|
|
183 |
|
|
|
184 |
```cpp
|
|
|
185 |
// Exact matching only
|
|
|
186 |
server.on(AsyncURIMatcher("/path", URIMatchExact), handler);
|
|
|
187 |
|
|
|
188 |
// Prefix matching only
|
|
|
189 |
server.on(AsyncURIMatcher("/api", URIMatchPrefix), handler);
|
|
|
190 |
|
|
|
191 |
// Combined flags
|
|
|
192 |
server.on(AsyncURIMatcher("/api", URIMatchPrefix | URIMatchCaseInsensitive), handler);
|
|
|
193 |
```
|
|
|
194 |
|
|
|
195 |
### Factory Functions
|
|
|
196 |
|
|
|
197 |
```cpp
|
|
|
198 |
// More readable and expressive
|
|
|
199 |
server.on(AsyncURIMatcher::exact("/login"), handler);
|
|
|
200 |
server.on(AsyncURIMatcher::prefix("/api"), handler);
|
|
|
201 |
server.on(AsyncURIMatcher::dir("/admin"), handler);
|
|
|
202 |
server.on(AsyncURIMatcher::ext("/images/*.jpg"), handler);
|
|
|
203 |
|
|
|
204 |
#ifdef ASYNCWEBSERVER_REGEX
|
|
|
205 |
server.on(AsyncURIMatcher::regex("^/user/([0-9]+)$"), handler);
|
|
|
206 |
#endif
|
|
|
207 |
```
|
|
|
208 |
|
|
|
209 |
## Available Flags
|
|
|
210 |
|
|
|
211 |
| Flag | Description |
|
|
|
212 |
| ------------------------- | ----------------------------------------------------------- |
|
|
|
213 |
| `URIMatchAuto` | Auto-detect match type from pattern (default) |
|
|
|
214 |
| `URIMatchExact` | Exact URL match |
|
|
|
215 |
| `URIMatchPrefix` | Prefix match |
|
|
|
216 |
| `URIMatchPrefixFolder` | Folder prefix match (requires trailing /) |
|
|
|
217 |
| `URIMatchExtension` | File extension match pattern |
|
|
|
218 |
| `URIMatchCaseInsensitive` | Case insensitive matching |
|
|
|
219 |
| `URIMatchRegex` | Regular expression matching (requires ASYNCWEBSERVER_REGEX) |
|
|
|
220 |
|
|
|
221 |
## Testing the Example
|
|
|
222 |
|
|
|
223 |
1. **Upload the sketch** to your ESP32/ESP8266
|
|
|
224 |
2. **Connect to WiFi AP**: `esp-captive` (no password required)
|
|
|
225 |
3. **Navigate to**: `http://192.168.4.1/`
|
|
|
226 |
4. **Explore the examples** by clicking the organized test links
|
|
|
227 |
5. **Monitor Serial output**: Open Serial Monitor to see detailed debugging information for each matched route
|
|
|
228 |
|
|
|
229 |
### Test URLs Available (All Clickable from Homepage)
|
|
|
230 |
|
|
|
231 |
**Auto-Detection Examples:**
|
|
|
232 |
|
|
|
233 |
- `http://192.168.4.1/auto` (exact + folder match)
|
|
|
234 |
- `http://192.168.4.1/auto/sub` (folder match - same handler!)
|
|
|
235 |
- `http://192.168.4.1/wildcard-test` (auto-detected prefix)
|
|
|
236 |
- `http://192.168.4.1/auto-images/photo.png` (auto-detected extension)
|
|
|
237 |
|
|
|
238 |
**Factory Method Examples:**
|
|
|
239 |
|
|
|
240 |
- `http://192.168.4.1/exact` (AsyncURIMatcher::exact)
|
|
|
241 |
- `http://192.168.4.1/service/status` (AsyncURIMatcher::prefix)
|
|
|
242 |
- `http://192.168.4.1/admin/users` (AsyncURIMatcher::dir)
|
|
|
243 |
- `http://192.168.4.1/images/photo.jpg` (AsyncURIMatcher::ext)
|
|
|
244 |
|
|
|
245 |
**Case Insensitive Examples:**
|
|
|
246 |
|
|
|
247 |
- `http://192.168.4.1/case` (lowercase)
|
|
|
248 |
- `http://192.168.4.1/CASE` (uppercase)
|
|
|
249 |
- `http://192.168.4.1/CaSe` (mixed case)
|
|
|
250 |
|
|
|
251 |
**Regex Examples (if ASYNCWEBSERVER_REGEX enabled):**
|
|
|
252 |
|
|
|
253 |
- `http://192.168.4.1/user/123` (captures numeric ID)
|
|
|
254 |
- `http://192.168.4.1/user/456` (captures numeric ID)
|
|
|
255 |
|
|
|
256 |
**Combined Flags Examples:**
|
|
|
257 |
|
|
|
258 |
- `http://192.168.4.1/mixedcase-test` (prefix + case insensitive)
|
|
|
259 |
- `http://192.168.4.1/MIXEDCASE/sub` (prefix + case insensitive)
|
|
|
260 |
|
|
|
261 |
### Console Output
|
|
|
262 |
|
|
|
263 |
Each handler provides detailed debugging information via Serial output:
|
|
|
264 |
|
|
|
265 |
```
|
|
|
266 |
Auto-Detection Match (Traditional)
|
|
|
267 |
Matched URL: /auto
|
|
|
268 |
Uses auto-detection: exact + folder matching
|
|
|
269 |
```
|
|
|
270 |
|
|
|
271 |
```
|
|
|
272 |
Factory Exact Match
|
|
|
273 |
Matched URL: /exact
|
|
|
274 |
Uses AsyncURIMatcher::exact() factory function
|
|
|
275 |
```
|
|
|
276 |
|
|
|
277 |
```
|
|
|
278 |
Regex Match - User ID
|
|
|
279 |
Matched URL: /user/123
|
|
|
280 |
Captured User ID: 123
|
|
|
281 |
This regex matches /user/{number} pattern
|
|
|
282 |
```
|
|
|
283 |
|
|
|
284 |
## Compilation Options
|
|
|
285 |
|
|
|
286 |
### Enable Regex Support
|
|
|
287 |
|
|
|
288 |
To enable regular expression matching, compile with:
|
|
|
289 |
|
|
|
290 |
```
|
|
|
291 |
-D ASYNCWEBSERVER_REGEX
|
|
|
292 |
```
|
|
|
293 |
|
|
|
294 |
In PlatformIO, add to `platformio.ini`:
|
|
|
295 |
|
|
|
296 |
```ini
|
|
|
297 |
build_flags = -D ASYNCWEBSERVER_REGEX
|
|
|
298 |
```
|
|
|
299 |
|
|
|
300 |
In Arduino IDE, add to your sketch:
|
|
|
301 |
|
|
|
302 |
```cpp
|
|
|
303 |
#define ASYNCWEBSERVER_REGEX
|
|
|
304 |
```
|
|
|
305 |
|
|
|
306 |
## Performance Considerations
|
|
|
307 |
|
|
|
308 |
1. **Exact matches** are fastest
|
|
|
309 |
2. **Prefix matches** are very efficient
|
|
|
310 |
3. **Regex matches** are slower but most flexible
|
|
|
311 |
4. **Case insensitive** matching adds minimal overhead
|
|
|
312 |
5. **Auto-detection** adds slight parsing overhead at construction time
|
|
|
313 |
|
|
|
314 |
## Real-World Applications
|
|
|
315 |
|
|
|
316 |
### REST API Design
|
|
|
317 |
|
|
|
318 |
```cpp
|
|
|
319 |
// API versioning
|
|
|
320 |
server.on(AsyncURIMatcher::prefix("/api/v1"), handleAPIv1);
|
|
|
321 |
server.on(AsyncURIMatcher::prefix("/api/v2"), handleAPIv2);
|
|
|
322 |
|
|
|
323 |
// Resource endpoints with IDs
|
|
|
324 |
server.on(AsyncURIMatcher::regex("^/api/users/([0-9]+)$"), handleUserById);
|
|
|
325 |
server.on(AsyncURIMatcher::regex("^/api/posts/([0-9]+)/comments$"), handlePostComments);
|
|
|
326 |
```
|
|
|
327 |
|
|
|
328 |
### File Serving
|
|
|
329 |
|
|
|
330 |
```cpp
|
|
|
331 |
// Serve different file types
|
|
|
332 |
server.on(AsyncURIMatcher::ext("/assets/*.css"), serveCSSFiles);
|
|
|
333 |
server.on(AsyncURIMatcher::ext("/assets/*.js"), serveJSFiles);
|
|
|
334 |
server.on(AsyncURIMatcher::ext("/images/*.jpg"), serveImageFiles);
|
|
|
335 |
```
|
|
|
336 |
|
|
|
337 |
### Admin Interface
|
|
|
338 |
|
|
|
339 |
```cpp
|
|
|
340 |
// Admin section with authentication
|
|
|
341 |
server.on(AsyncURIMatcher::dir("/admin"), handleAdminPages);
|
|
|
342 |
server.on(AsyncURIMatcher::exact("/admin"), redirectToAdminDashboard);
|
|
|
343 |
```
|
|
|
344 |
|
|
|
345 |
## See Also
|
|
|
346 |
|
|
|
347 |
- [ESPAsyncWebServer Documentation](https://github.com/ESP32Async/ESPAsyncWebServer)
|
|
|
348 |
- [Regular Expression Reference](https://en.cppreference.com/w/cpp/regex)
|
|
|
349 |
- Other examples in the `examples/` directory
|