-
Notifications
You must be signed in to change notification settings - Fork 160
Expand file tree
/
Copy pathWebResponse.cs
More file actions
145 lines (113 loc) · 4.51 KB
/
WebResponse.cs
File metadata and controls
145 lines (113 loc) · 4.51 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
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.ClearScript.Util.Web
{
internal sealed class WebResponse : IDisposable
{
private readonly Socket socket;
private int state;
public int StatusCode { get; set; }
public string ContentType { get; set; }
public Stream OutputStream { get; }
internal WebResponse(Socket socket, int statusCode)
{
this.socket = socket;
state = State.Open;
StatusCode = statusCode;
OutputStream = new MemoryStream();
}
internal async Task<WebSocket> AcceptWebSocketAsync(string key)
{
if (Interlocked.CompareExchange(ref state, State.Upgraded, State.Open) == State.Open)
{
using (var stream = CreateWebSocketResponseStream(key))
{
await socket.SendBytesAsync(stream.GetBuffer(), 0, Convert.ToInt32(stream.Length)).ConfigureAwait(false);
}
return new WebSocket(socket, true);
}
throw new InvalidOperationException("Cannot accept a WebSocket connection in the current state");
}
public void Close(int? overrideStatusCode = null)
{
if (Interlocked.CompareExchange(ref state, State.Closed, State.Open) == State.Open)
{
CloseAsync(overrideStatusCode).ContinueWith(task => MiscHelpers.Try(static task => task.Wait(), task));
}
}
private async Task CloseAsync(int? overrideStatusCode)
{
using (socket)
{
using (var stream = CreateResponseStream(overrideStatusCode))
{
await socket.SendBytesAsync(stream.GetBuffer(), 0, Convert.ToInt32(stream.Length)).ConfigureAwait(false);
}
}
}
private MemoryStream CreateResponseStream(int? overrideStatusCode)
{
var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, Encoding.ASCII, 16 * 1024, true))
{
var statusCode = overrideStatusCode ?? StatusCode;
writer.Write("HTTP/1.1 {0} {1}\r\n", statusCode, Enum.GetName(typeof(HttpStatusCode), statusCode) ?? string.Empty);
if (!string.IsNullOrWhiteSpace(ContentType))
{
writer.Write("Content-Type: {0}\r\n", ContentType);
}
if (OutputStream.Length > 0)
{
writer.Write("Content-Length: {0}\r\n", OutputStream.Length);
}
writer.Write("Cache-Control: no-cache, no-store, must-revalidate\r\n");
writer.Write("Connection: close\r\n");
writer.Write("\r\n");
writer.Flush();
}
if (OutputStream.Length > 0)
{
stream.Write(((MemoryStream)OutputStream).GetBuffer(), 0, Convert.ToInt32(OutputStream.Length));
}
return stream;
}
private static MemoryStream CreateWebSocketResponseStream(string key)
{
var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, Encoding.ASCII, 16 * 1024, true))
{
const int statusCode = 101;
writer.Write("HTTP/1.1 {0} {1}\r\n", statusCode, Enum.GetName(typeof(HttpStatusCode), statusCode) ?? string.Empty);
writer.Write("Connection: Upgrade\r\n");
writer.Write("Upgrade: websocket\r\n");
var acceptKey = Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")));
writer.Write("Sec-WebSocket-Accept: {0}\r\n", acceptKey);
writer.Write("\r\n");
writer.Flush();
}
return stream;
}
#region IDisposable implementation
public void Dispose()
{
Close();
}
#endregion
#region Nested type: State
private static class State
{
public const int Open = 0;
public const int Upgraded = 1;
public const int Closed = 2;
}
#endregion
}
}