0
# HTTP Utilities
1
2
Comprehensive HTTP parsing and formatting utilities for headers, cookies, dates, ETags, cache control, and content negotiation. These low-level functions provide the foundation for HTTP protocol handling in Werkzeug.
3
4
## Capabilities
5
6
### Header Parsing and Formatting
7
8
Functions for parsing and formatting HTTP headers with proper escaping and encoding.
9
10
```python { .api }
11
def quote_header_value(value, allow_token=True):
12
"""
13
Add double quotes around a header value if needed.
14
15
Parameters:
16
- value: The value to quote (will be converted to string)
17
- allow_token: If False, always quote even if value contains only token chars
18
19
Returns:
20
Quoted header value string
21
"""
22
23
def unquote_header_value(value):
24
"""
25
Remove quotes from a header value and unescape internal quotes.
26
27
Parameters:
28
- value: Quoted header value string
29
30
Returns:
31
Unquoted header value
32
"""
33
34
def parse_list_header(value):
35
"""
36
Parse a header that contains a comma-separated list.
37
38
Parameters:
39
- value: Header value string
40
41
Returns:
42
List of individual values
43
"""
44
45
def parse_dict_header(value):
46
"""
47
Parse a header that contains key=value pairs.
48
49
Parameters:
50
- value: Header value string with key=value pairs
51
52
Returns:
53
Dictionary of key-value pairs
54
"""
55
56
def parse_options_header(value):
57
"""
58
Parse a Content-Type-like header with main value and parameters.
59
60
Parameters:
61
- value: Header value string (e.g., "text/html; charset=utf-8")
62
63
Returns:
64
Tuple of (main_value, parameters_dict)
65
"""
66
67
def dump_options_header(header, options):
68
"""
69
Create a Content-Type-like header from main value and options.
70
71
Parameters:
72
- header: Main header value
73
- options: Dictionary of parameters
74
75
Returns:
76
Complete header string
77
"""
78
79
def dump_header(iterable):
80
"""
81
Dump an iterable into a header value.
82
83
Parameters:
84
- iterable: Dictionary or iterable to convert to header
85
86
Returns:
87
Header string representation
88
"""
89
```
90
91
### Accept Header Processing
92
93
Functions for parsing and handling HTTP Accept headers with quality values.
94
95
```python { .api }
96
def parse_accept_header(value, cls=None):
97
"""
98
Parse an Accept header into structured data.
99
100
Parameters:
101
- value: Accept header string
102
- cls: Accept class to use (Accept, MIMEAccept, LanguageAccept, CharsetAccept)
103
104
Returns:
105
Accept object with parsed values and quality scores
106
"""
107
```
108
109
### Cache Control Headers
110
111
Functions for parsing and managing Cache-Control headers.
112
113
```python { .api }
114
def parse_cache_control_header(value, on_update=None, cls=None):
115
"""
116
Parse Cache-Control header into structured object.
117
118
Parameters:
119
- value: Cache-Control header string
120
- on_update: Callback function for updates
121
- cls: Cache control class (RequestCacheControl or ResponseCacheControl)
122
123
Returns:
124
Cache control object with directive properties
125
"""
126
```
127
128
### Content Security Policy
129
130
Functions for parsing and formatting CSP headers.
131
132
```python { .api }
133
def parse_csp_header(value, on_update=None, cls=None):
134
"""
135
Parse Content-Security-Policy header.
136
137
Parameters:
138
- value: CSP header string
139
- on_update: Callback function for updates
140
- cls: CSP class to use
141
142
Returns:
143
ContentSecurityPolicy object
144
"""
145
146
def dump_csp_header(header):
147
"""
148
Convert CSP object to header string.
149
150
Parameters:
151
- header: ContentSecurityPolicy object
152
153
Returns:
154
CSP header string
155
"""
156
```
157
158
### Set Headers
159
160
Functions for parsing set-like headers.
161
162
```python { .api }
163
def parse_set_header(value, on_update=None, cls=None):
164
"""
165
Parse set-like headers (comma-separated values).
166
167
Parameters:
168
- value: Header value string
169
- on_update: Callback for updates
170
- cls: Set class to use
171
172
Returns:
173
HeaderSet object
174
"""
175
```
176
177
### ETag Functions
178
179
Functions for generating, parsing, and validating ETags.
180
181
```python { .api }
182
def generate_etag(data):
183
"""
184
Generate an ETag from data using SHA-1 hash.
185
186
Parameters:
187
- data: Raw data bytes to hash
188
189
Returns:
190
ETag string (quoted hash)
191
"""
192
193
def quote_etag(etag, weak=False):
194
"""
195
Quote an ETag value for use in headers.
196
197
Parameters:
198
- etag: ETag value to quote
199
- weak: Whether this is a weak ETag (prepends W/)
200
201
Returns:
202
Quoted ETag string
203
"""
204
205
def unquote_etag(etag):
206
"""
207
Unquote an ETag and determine if it's weak.
208
209
Parameters:
210
- etag: Quoted ETag string or None
211
212
Returns:
213
Tuple of (unquoted_etag, is_weak)
214
"""
215
216
def parse_etags(value):
217
"""
218
Parse ETags header (If-Match, If-None-Match).
219
220
Parameters:
221
- value: ETags header string
222
223
Returns:
224
ETags object containing strong/weak etags and star flag
225
"""
226
227
def is_resource_modified(environ, etag=None, data=None, last_modified=None, ignore_if_range=True):
228
"""
229
Check if a resource was modified based on conditional headers.
230
231
Parameters:
232
- environ: WSGI environment or Request object
233
- etag: Current ETag of the resource
234
- data: Raw data to generate ETag from
235
- last_modified: Last modified timestamp
236
- ignore_if_range: Whether to ignore If-Range header
237
238
Returns:
239
True if resource was modified (should send full response)
240
"""
241
```
242
243
### Date and Time Functions
244
245
Functions for parsing and formatting HTTP dates.
246
247
```python { .api }
248
def http_date(timestamp=None):
249
"""
250
Format a timestamp as HTTP date string.
251
252
Parameters:
253
- timestamp: Unix timestamp, datetime, or None for current time
254
255
Returns:
256
HTTP date string (RFC 7231 format)
257
"""
258
259
def parse_date(value):
260
"""
261
Parse HTTP date string to datetime object.
262
263
Parameters:
264
- value: HTTP date string
265
266
Returns:
267
datetime object or None if parsing failed
268
"""
269
270
def parse_age(value=None):
271
"""
272
Parse Age header value.
273
274
Parameters:
275
- value: Age header string
276
277
Returns:
278
timedelta object representing the age
279
"""
280
281
def dump_age(age=None):
282
"""
283
Format age as Age header string.
284
285
Parameters:
286
- age: timedelta, int (seconds), or None
287
288
Returns:
289
Age header string
290
"""
291
```
292
293
### Cookie Functions
294
295
Functions for parsing and creating HTTP cookies.
296
297
```python { .api }
298
def parse_cookie(header, cls=None):
299
"""
300
Parse Cookie header into dictionary.
301
302
Parameters:
303
- header: Cookie header string
304
- cls: Dictionary class to use for result
305
306
Returns:
307
Dictionary of cookie name-value pairs
308
"""
309
310
def dump_cookie(key, value="", max_age=None, expires=None, path="/", domain=None, secure=False, httponly=False, charset="utf-8", sync_expires=True, max_size=4093, samesite=None):
311
"""
312
Create Set-Cookie header string.
313
314
Parameters:
315
- key: Cookie name
316
- value: Cookie value
317
- max_age: Maximum age in seconds
318
- expires: Expiration date (datetime or timestamp)
319
- path: Cookie path
320
- domain: Cookie domain
321
- secure: Only send over HTTPS
322
- httponly: Prevent JavaScript access
323
- charset: Character encoding for value
324
- sync_expires: Sync expires with max_age
325
- max_size: Maximum cookie size
326
- samesite: SameSite attribute ('Strict', 'Lax', 'None')
327
328
Returns:
329
Complete Set-Cookie header string
330
"""
331
```
332
333
### Range Header Functions
334
335
Functions for handling HTTP Range requests.
336
337
```python { .api }
338
def parse_range_header(value, make_inclusive=True):
339
"""
340
Parse Range header for partial content requests.
341
342
Parameters:
343
- value: Range header string (e.g., "bytes=0-1023")
344
- make_inclusive: Whether to make ranges inclusive
345
346
Returns:
347
Range object with units and range specifications
348
"""
349
350
def parse_content_range_header(value, on_update=None, cls=None):
351
"""
352
Parse Content-Range header from partial content responses.
353
354
Parameters:
355
- value: Content-Range header string
356
- on_update: Callback for updates
357
- cls: ContentRange class to use
358
359
Returns:
360
ContentRange object
361
"""
362
363
def parse_if_range_header(value):
364
"""
365
Parse If-Range header (ETag or date).
366
367
Parameters:
368
- value: If-Range header string
369
370
Returns:
371
IfRange object containing ETag or date
372
"""
373
374
def is_byte_range_valid(start, stop, length):
375
"""
376
Check if a byte range is valid for given content length.
377
378
Parameters:
379
- start: Range start position
380
- stop: Range stop position
381
- length: Total content length
382
383
Returns:
384
True if range is valid
385
"""
386
```
387
388
### Header Classification
389
390
Functions for classifying and filtering HTTP headers.
391
392
```python { .api }
393
def is_entity_header(header):
394
"""
395
Check if header is an entity header.
396
397
Entity headers describe the content of the message body.
398
399
Parameters:
400
- header: Header name
401
402
Returns:
403
True if header is an entity header
404
"""
405
406
def is_hop_by_hop_header(header):
407
"""
408
Check if header is a hop-by-hop header.
409
410
Hop-by-hop headers are meaningful only for a single connection.
411
412
Parameters:
413
- header: Header name
414
415
Returns:
416
True if header is hop-by-hop
417
"""
418
419
def remove_entity_headers(headers, allowed=None):
420
"""
421
Remove entity headers from header collection.
422
423
Parameters:
424
- headers: Headers object or list of (name, value) tuples
425
- allowed: Set of entity headers to keep
426
427
Returns:
428
New Headers object without entity headers
429
"""
430
431
def remove_hop_by_hop_headers(headers):
432
"""
433
Remove hop-by-hop headers from header collection in-place.
434
435
Parameters:
436
- headers: Headers object or list of (name, value) tuples
437
"""
438
```
439
440
### Constants and Enums
441
442
HTTP status codes and policy enums.
443
444
```python { .api }
445
# HTTP status code mappings
446
HTTP_STATUS_CODES: dict[int, str] = {
447
100: "Continue",
448
200: "OK",
449
201: "Created",
450
204: "No Content",
451
301: "Moved Permanently",
452
302: "Found",
453
304: "Not Modified",
454
400: "Bad Request",
455
401: "Unauthorized",
456
403: "Forbidden",
457
404: "Not Found",
458
405: "Method Not Allowed",
459
500: "Internal Server Error",
460
# ... complete mapping available
461
}
462
463
class COEP(Enum):
464
"""Cross Origin Embedder Policy values."""
465
UNSAFE_NONE = "unsafe-none"
466
REQUIRE_CORP = "require-corp"
467
468
class COOP(Enum):
469
"""Cross Origin Opener Policy values."""
470
UNSAFE_NONE = "unsafe-none"
471
SAME_ORIGIN_ALLOW_POPUPS = "same-origin-allow-popups"
472
SAME_ORIGIN = "same-origin"
473
```
474
475
## Usage Examples
476
477
### Content Type and Accept Headers
478
479
```python
480
from werkzeug.http import parse_options_header, dump_options_header, parse_accept_header
481
482
def handle_content_negotiation(request):
483
# Parse Content-Type header
484
content_type, params = parse_options_header(request.headers.get('Content-Type'))
485
charset = params.get('charset', 'utf-8')
486
487
print(f"Content type: {content_type}, Charset: {charset}")
488
# Output: Content type: application/json, Charset: utf-8
489
490
# Create Content-Type header
491
ct_header = dump_options_header('text/html', {'charset': 'utf-8'})
492
print(ct_header) # "text/html; charset=utf-8"
493
494
# Parse Accept header
495
accept = parse_accept_header(request.headers.get('Accept'))
496
best_match = accept.best_match(['text/html', 'application/json', 'text/plain'])
497
498
if best_match == 'application/json':
499
return create_json_response()
500
elif best_match == 'text/html':
501
return create_html_response()
502
else:
503
return create_plain_text_response()
504
```
505
506
### ETag Generation and Validation
507
508
```python
509
from werkzeug.http import generate_etag, is_resource_modified, quote_etag
510
from werkzeug.wrappers import Response
511
import json
512
513
def serve_with_etag(request, data):
514
# Generate ETag from data
515
data_bytes = json.dumps(data, sort_keys=True).encode('utf-8')
516
etag = generate_etag(data_bytes)
517
518
# Check if resource was modified
519
if not is_resource_modified(request.environ, etag=etag):
520
# Return 304 Not Modified
521
response = Response(status=304)
522
response.headers['ETag'] = quote_etag(etag)
523
return response
524
525
# Return full response with ETag
526
response = Response(json.dumps(data), mimetype='application/json')
527
response.headers['ETag'] = quote_etag(etag)
528
return response
529
```
530
531
### Cookie Handling
532
533
```python
534
from werkzeug.http import parse_cookie, dump_cookie
535
from werkzeug.wrappers import Request, Response
536
from datetime import datetime, timedelta
537
538
def session_management(environ, start_response):
539
request = Request(environ)
540
541
# Parse incoming cookies
542
cookies = parse_cookie(request.headers.get('Cookie', ''))
543
session_id = cookies.get('session_id')
544
545
response = Response("Session handling example")
546
547
if not session_id:
548
# Create new session
549
session_id = generate_session_id() # Your function
550
551
# Set secure session cookie
552
cookie_header = dump_cookie(
553
'session_id',
554
session_id,
555
max_age=3600, # 1 hour
556
path='/',
557
secure=True,
558
httponly=True,
559
samesite='Lax'
560
)
561
response.headers['Set-Cookie'] = cookie_header
562
563
# Set additional cookies
564
theme_cookie = dump_cookie(
565
'theme',
566
'dark',
567
expires=datetime.now() + timedelta(days=30),
568
path='/'
569
)
570
response.headers.add('Set-Cookie', theme_cookie)
571
572
return response(environ, start_response)
573
```
574
575
### Range Request Handling
576
577
```python
578
from werkzeug.http import parse_range_header, is_byte_range_valid
579
from werkzeug.wrappers import Request, Response
580
import os
581
582
def serve_file_with_ranges(request, file_path):
583
file_size = os.path.getsize(file_path)
584
585
# Parse Range header
586
range_header = request.headers.get('Range')
587
if range_header:
588
try:
589
ranges = parse_range_header(range_header)
590
if ranges and ranges.ranges:
591
# Get first range
592
start, end = ranges.ranges[0]
593
594
# Handle open-ended ranges
595
if start is None:
596
start = max(0, file_size - end)
597
end = file_size
598
elif end is None or end >= file_size:
599
end = file_size
600
601
# Validate range
602
if is_byte_range_valid(start, end, file_size):
603
# Serve partial content
604
with open(file_path, 'rb') as f:
605
f.seek(start)
606
data = f.read(end - start)
607
608
response = Response(
609
data,
610
status=206, # Partial Content
611
headers={
612
'Content-Range': f'bytes {start}-{end-1}/{file_size}',
613
'Accept-Ranges': 'bytes',
614
'Content-Length': str(len(data))
615
}
616
)
617
return response
618
except (ValueError, IndexError):
619
pass # Invalid range, serve full file
620
621
# Serve full file
622
with open(file_path, 'rb') as f:
623
data = f.read()
624
625
response = Response(
626
data,
627
headers={
628
'Accept-Ranges': 'bytes',
629
'Content-Length': str(file_size)
630
}
631
)
632
return response
633
```
634
635
### Cache Control Headers
636
637
```python
638
from werkzeug.http import parse_cache_control_header
639
from werkzeug.datastructures import ResponseCacheControl
640
from werkzeug.wrappers import Response
641
from datetime import datetime, timedelta
642
643
def handle_caching(request):
644
# Parse request cache control
645
cache_control = parse_cache_control_header(
646
request.headers.get('Cache-Control')
647
)
648
649
if cache_control.no_cache:
650
# Client doesn't want cached response
651
response = generate_fresh_response()
652
elif cache_control.max_age is not None:
653
# Client specifies max age
654
response = get_cached_response_if_fresh(cache_control.max_age)
655
else:
656
response = get_normal_response()
657
658
# Set response cache control
659
response.cache_control.public = True
660
response.cache_control.max_age = 3600 # 1 hour
661
response.cache_control.must_revalidate = True
662
663
# Set other caching headers
664
response.expires = datetime.utcnow() + timedelta(hours=1)
665
response.last_modified = datetime.utcnow()
666
667
return response
668
```
669
670
### Date and Time Handling
671
672
```python
673
from werkzeug.http import http_date, parse_date, parse_age, dump_age
674
from datetime import datetime, timedelta
675
676
def date_header_examples():
677
# Current time as HTTP date
678
now_header = http_date()
679
print(now_header) # "Wed, 21 Oct 2015 07:28:00 GMT"
680
681
# Specific timestamp
682
timestamp = 1445412480
683
date_header = http_date(timestamp)
684
685
# Parse HTTP date
686
parsed = parse_date("Wed, 21 Oct 2015 07:28:00 GMT")
687
print(parsed) # datetime(2015, 10, 21, 7, 28, 0)
688
689
# Age header handling
690
age = timedelta(hours=2, minutes=30)
691
age_header = dump_age(age)
692
print(age_header) # "9000" (seconds)
693
694
parsed_age = parse_age("9000")
695
print(parsed_age) # timedelta(seconds=9000)
696
```
697
698
### Content Security Policy
699
700
```python
701
from werkzeug.datastructures import ContentSecurityPolicy
702
from werkzeug.http import dump_csp_header, parse_csp_header
703
704
def setup_csp():
705
# Create CSP policy
706
csp = ContentSecurityPolicy()
707
csp.default_src = ["'self'"]
708
csp.script_src = ["'self'", "'unsafe-inline'", "https://cdn.example.com"]
709
csp.style_src = ["'self'", "'unsafe-inline'"]
710
csp.img_src = ["'self'", "data:", "https:"]
711
712
# Convert to header string
713
csp_header = dump_csp_header(csp)
714
715
# Parse existing CSP header
716
existing_csp = "default-src 'self'; script-src 'self' 'unsafe-inline'"
717
parsed_csp = parse_csp_header(existing_csp)
718
719
return csp_header
720
```
721
722
### Header Utilities
723
724
```python
725
from werkzeug.http import (
726
quote_header_value, unquote_header_value,
727
remove_entity_headers, is_hop_by_hop_header
728
)
729
from werkzeug.datastructures import Headers
730
731
def header_processing():
732
# Quote/unquote header values
733
quoted = quote_header_value("filename with spaces.txt")
734
print(quoted) # '"filename with spaces.txt"'
735
736
unquoted = unquote_header_value(quoted)
737
print(unquoted) # "filename with spaces.txt"
738
739
# Header classification
740
print(is_hop_by_hop_header('Connection')) # True
741
print(is_hop_by_hop_header('Content-Type')) # False
742
743
# Filter headers for forwarding
744
headers = Headers([
745
('Content-Type', 'text/html'),
746
('Content-Length', '1234'),
747
('Connection', 'close'),
748
('X-Custom', 'value')
749
])
750
751
# Remove entity headers (for HEAD requests)
752
filtered = remove_entity_headers(headers, allowed={'Content-Type'})
753
# Result has Content-Type and X-Custom, but not Content-Length
754
```
755
756
## Form Data Parsing
757
758
Low-level utilities for parsing form data from HTTP requests, including multipart and URL-encoded data handling.
759
760
```python { .api }
761
def parse_form_data(environ, stream_factory=None, max_form_memory_size=None, max_content_length=None, cls=None, silent=True, *, max_form_parts=None):
762
"""
763
Parse form data from WSGI environ.
764
765
Parameters:
766
- environ: WSGI environment dictionary
767
- stream_factory: Callable for creating file streams
768
- max_form_memory_size: Maximum memory for form data
769
- max_content_length: Maximum total content length
770
- cls: MultiDict class to use (default: MultiDict)
771
- silent: Don't raise on parsing errors
772
- max_form_parts: Maximum number of multipart parts
773
774
Returns:
775
Tuple of (stream, form_dict, files_dict)
776
"""
777
778
def default_stream_factory(total_content_length, content_type, filename, content_length=None):
779
"""
780
Default stream factory for file uploads.
781
782
Parameters:
783
- total_content_length: Total request content length
784
- content_type: Content type of the part
785
- filename: Filename if provided
786
- content_length: Content length of this part
787
788
Returns:
789
File-like object for storing upload data
790
"""
791
792
class FormDataParser:
793
def __init__(self, stream_factory=None, max_form_memory_size=None, max_content_length=None, cls=None, silent=True, *, max_form_parts=None):
794
"""
795
Advanced form data parser with configurable limits.
796
797
Parameters:
798
- stream_factory: Callable for creating file streams
799
- max_form_memory_size: Maximum memory for form data
800
- max_content_length: Maximum total content length
801
- cls: MultiDict class to use
802
- silent: Don't raise on parsing errors
803
- max_form_parts: Maximum number of multipart parts
804
"""
805
806
def parse_from_environ(self, environ):
807
"""
808
Parse form data from WSGI environment.
809
810
Parameters:
811
- environ: WSGI environment dictionary
812
813
Returns:
814
Tuple of (stream, form_dict, files_dict)
815
"""
816
```