-
Notifications
You must be signed in to change notification settings - Fork 513
/
Copy pathHttpRequest.cs
166 lines (143 loc) · 5.58 KB
/
HttpRequest.cs
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
158
159
160
161
162
163
164
165
166
using RuriLib.Http.Extensions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace RuriLib.Http.Models
{
public class HttpRequest : IDisposable
{
public bool AbsoluteUriInFirstLine { get; set; } = false;
public Version Version { get; set; } = new(1, 1);
public HttpMethod Method { get; set; } = HttpMethod.Get;
public Uri Uri { get; set; }
public Dictionary<string, string> Cookies { get; set; } = new();
public Dictionary<string, string> Headers { get; set; } = new();
public HttpContent Content { get; set; }
public async Task<byte[]> GetBytesAsync(CancellationToken cancellationToken = default)
{
using var ms = new MemoryStream();
ms.Write(Encoding.ASCII.GetBytes(BuildFirstLine()));
ms.Write(Encoding.ASCII.GetBytes(BuildHeaders()));
if (Content != null)
{
ms.Write(await Content.ReadAsByteArrayAsync(cancellationToken));
}
return ms.ToArray();
}
private static readonly string newLine = "\r\n";
/// <summary>
/// Safely adds a header to the dictionary.
/// </summary>
public void AddHeader(string name, string value)
{
// Make sure Host is written properly otherwise it won't get picked up below
if (name.Equals("Host", StringComparison.OrdinalIgnoreCase))
{
Headers["Host"] = value;
}
else
{
Headers[name] = value;
}
}
// Builds the first line, for example
// GET /resource HTTP/1.1
private string BuildFirstLine()
{
if (Version >= new Version(2, 0))
throw new Exception($"HTTP/{Version.Major}.{Version.Minor} not supported yet");
return $"{Method.Method} {(AbsoluteUriInFirstLine ? Uri.AbsoluteUri : Uri.PathAndQuery)} HTTP/{Version}{newLine}";
}
// Builds the headers, for example
// Host: example.com
// Connection: Close
private string BuildHeaders()
{
// NOTE: Do not use AppendLine because it appends \n instead of \r\n
// on Unix-like systems.
var sb = new StringBuilder();
var finalHeaders = new List<KeyValuePair<string, string>>();
// Add the Host header if not already provided
if (!HeaderExists("Host", out _))
{
finalHeaders.Add("Host", Uri.Host);
}
// TODO: Implement support for Keep-Alive connections!
// If there is already a Connection header
if (HeaderExists("Connection", out var connectionHeaderName))
{
// If its value is not Close, change it to Close
if (!Headers[connectionHeaderName].Equals("Close", StringComparison.OrdinalIgnoreCase))
{
Headers[connectionHeaderName] = "Close";
}
}
// Otherwise, add it
else
{
finalHeaders.Add("Connection", "Close");
}
// Add the non-content headers
foreach (var header in Headers)
{
finalHeaders.Add(header);
}
// Add the Cookie header if not set manually and container not null
if (!HeaderExists("Cookie", out _) && Cookies.Any())
{
var cookieBuilder = new StringBuilder();
foreach (var cookie in Cookies)
{
cookieBuilder
.Append($"{cookie.Key}={cookie.Value}; ");
}
finalHeaders.Add("Cookie", cookieBuilder);
}
// Add the content headers
if (Content != null)
{
foreach (var header in Content.Headers)
{
// If it was already set, skip
if (!HeaderExists(header.Key, out _))
{
finalHeaders.Add(header.Key, string.Join(' ', header.Value));
}
}
// Add the Content-Length header if not already present
if (!finalHeaders.Any(h => h.Key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase)))
{
var contentLength = Content.Headers.ContentLength;
if (contentLength.HasValue && contentLength.Value > 0)
{
finalHeaders.Add("Content-Length", contentLength);
}
}
}
// Write all non-empty headers to the StringBuilder
foreach (var header in finalHeaders.Where(h => !string.IsNullOrEmpty(h.Value)))
{
sb
.Append(header.Key)
.Append(": ")
.Append(header.Value)
.Append(newLine);
}
// Write the final blank line after all headers
sb.Append(newLine);
return sb.ToString();
}
public bool HeaderExists(string name, out string actualName)
{
var key = Headers.Keys.FirstOrDefault(k => k.Equals(name, StringComparison.OrdinalIgnoreCase));
actualName = key;
return key != null;
}
public void Dispose() => Content?.Dispose();
}
}