private static async Task<string> ReadResponseHeaderLineAsync(Stream stream, CancellationToken cancellationToken)
{
StringBuilder sb = t_cachedStringBuilder;
if (sb != null)
{
t_cachedStringBuilder = null;
Debug.Assert(sb.Length == 0, $"Expected empty StringBuilder");
}
else
{
sb = new StringBuilder();
}
var arr = new byte[1];
char prevChar = '\0';
try
{
// TODO: Reading one byte is extremely inefficient. The problem, however,
// is that if we read multiple bytes, we could end up reading bytes post-headers
// that are part of messages meant to be read by the managed websocket after
// the connection. The likely solution here is to wrap the stream in a BufferedStream,
// though a) that comes at the expense of an extra set of virtual calls, b)
// it adds a buffer when the managed websocket will already be using a buffer, and
// c) it's not exposed on the version of the System.IO contract we're currently using.
while (await stream.ReadAsync(arr, 0, 1, cancellationToken).ConfigureAwait(false) == 1)
{
// Process the next char
char curChar = (char)arr[0];
if (prevChar == '\r' && curChar == '\n')
{
break;
}
sb.Append(curChar);
prevChar = curChar;
}
if (sb.Length > 0 && sb[sb.Length - 1] == '\r')
{
sb.Length = sb.Length - 1;
}
return sb.ToString();
}
finally
{
sb.Clear();
t_cachedStringBuilder = sb;
}
}
}