-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHeaderDictionary.cs
More file actions
157 lines (128 loc) · 5.12 KB
/
Copy pathHeaderDictionary.cs
File metadata and controls
157 lines (128 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.Extensions.Primitives;
namespace Shiny.Net.HttpServer;
/// <summary>
/// A case-insensitive, multi-value header collection. Shaped after ASP.NET Core's
/// <c>IHeaderDictionary</c> so existing muscle memory transfers.
/// </summary>
public sealed class HeaderDictionary : IDictionary<string, StringValues>
{
readonly Dictionary<string, StringValues> store;
public HeaderDictionary(int capacity = 8)
=> this.store = new Dictionary<string, StringValues>(capacity, StringComparer.OrdinalIgnoreCase);
/// <summary>
/// When true, mutation throws. Set once a response has begun writing to the wire,
/// since headers are gone by then and a silent no-op would be a debugging nightmare.
/// </summary>
public bool IsReadOnly { get; internal set; }
/// <summary>
/// Gets or sets a header. Unlike a plain dictionary, reading a missing header returns
/// <see cref="StringValues.Empty"/> rather than throwing, and assigning
/// <see cref="StringValues.Empty"/> removes it.
/// </summary>
public StringValues this[string key]
{
get => this.store.TryGetValue(key, out var value) ? value : StringValues.Empty;
set
{
this.ThrowIfReadOnly();
if (StringValues.IsNullOrEmpty(value))
this.store.Remove(key);
else
this.store[key] = value;
}
}
public int Count => this.store.Count;
public ICollection<string> Keys => this.store.Keys;
public ICollection<StringValues> Values => this.store.Values;
/// <summary>Appends a value, preserving any existing values for the same header.</summary>
public void Append(string key, string value)
{
this.ThrowIfReadOnly();
this.store[key] = this.store.TryGetValue(key, out var existing)
? StringValues.Concat(existing, value)
: new StringValues(value);
}
/// <summary>Replaces all values for a header with a single value.</summary>
public void Set(string key, string? value)
{
this.ThrowIfReadOnly();
if (value is null)
this.store.Remove(key);
else
this.store[key] = new StringValues(value);
}
/// <summary>Gets the first value for a header, or null when absent.</summary>
public string? GetFirst(string key)
=> this.store.TryGetValue(key, out var value) ? value.ToString() : null;
public void Add(string key, StringValues value)
{
this.ThrowIfReadOnly();
this.store.Add(key, value);
}
public void Add(KeyValuePair<string, StringValues> item) => this.Add(item.Key, item.Value);
public void Clear()
{
this.ThrowIfReadOnly();
this.store.Clear();
}
public bool Contains(KeyValuePair<string, StringValues> item)
=> this.store.TryGetValue(item.Key, out var value) && value.Equals(item.Value);
public bool ContainsKey(string key) => this.store.ContainsKey(key);
public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
=> ((ICollection<KeyValuePair<string, StringValues>>)this.store).CopyTo(array, arrayIndex);
public bool Remove(string key)
{
this.ThrowIfReadOnly();
return this.store.Remove(key);
}
public bool Remove(KeyValuePair<string, StringValues> item) => this.Remove(item.Key);
public bool TryGetValue(string key, out StringValues value) => this.store.TryGetValue(key, out value);
public Dictionary<string, StringValues>.Enumerator GetEnumerator() => this.store.GetEnumerator();
IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
=> this.store.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.store.GetEnumerator();
// ---- Strongly typed access to the headers that actually drive protocol behaviour ----
public long? ContentLength
{
get
{
var raw = this.GetFirst(HeaderNames.ContentLength);
if (raw is null)
return null;
return long.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed) && parsed >= 0
? parsed
: null;
}
set => this.Set(
HeaderNames.ContentLength,
value?.ToString(CultureInfo.InvariantCulture)
);
}
public string? ContentType
{
get => this.GetFirst(HeaderNames.ContentType);
set => this.Set(HeaderNames.ContentType, value);
}
public string? Host
{
get => this.GetFirst(HeaderNames.Host);
set => this.Set(HeaderNames.Host, value);
}
internal void Reset()
{
this.IsReadOnly = false;
this.store.Clear();
}
void ThrowIfReadOnly()
{
if (this.IsReadOnly)
ThrowReadOnly();
[DoesNotReturn]
static void ThrowReadOnly() => throw new InvalidOperationException(
"Headers are read-only because the response has already started. Set headers before writing to the body."
);
}
}