In my last post, I discussed how Windows Phone and Store apps will refuse to parse HTTP responses (namely headers) that do not strictly follow RFC2616. After a few weeks of waiting for Mashape to fix their responses, I thought it might be worth it to explore other options.
The first idea was to implement a custom handler, but that quickly ended when I found out that the system's handler gets called first for a response.
The above image used to be hosted at http://www.asp.net/web-api/overview/web-api-clients/httpclient-message-handlers but the page disappeared in the last day or two.
After stumbling through the usual mix of Windows Phone 8.1 Silverlight vs Store (RT) docs, I came across an RT class that is also available on Silverlight: StreamSocket
. Knowing very little about the protocol, this page and the RFC page on Messages the were a great resources.
It turns out (ignoring error handling, edge cases, etc. etc.) that you can implement your own HTTP client fairly simply!
var hostname = new HostName("www.w3.org");
var socket = new StreamSocket();
await socket.ConnectAsync(hostname, "80");
var request = "GET /Protocols/rfc2616/rfc2616-sec4.html HTTP/1.1\r\n" +
"Host: www.w3.org\r\n" +
"\r\n";
var writer = new DataWriter(socket.OutputStream);
writer.WriteString(request);
await writer.StoreAsync();
var reader = new DataReader(socket.InputStream);
reader.InputStreamOptions = InputStreamOptions.Partial;
string data = string.Empty;
var cts = new CancellationTokenSource();
bool doneReading = false;
uint bytesToRead = 4096;
uint totalBytesRead = 0;
while (!doneReading)
{
try
{
cts.CancelAfter(10 * 1000);
var bytesRead =
await reader.LoadAsync(bytesToRead).AsTask(cts.Token);
data += reader.ReadString(reader.UnconsumedBufferLength);
totalBytesRead += bytesRead;
}
catch (TaskCanceledException)
{
doneReading = true;
}
}
socket.Dispose();
Since LoadAsync
returns the # of bytes loaded into the stream, you might think (as I did) that if it was less than the bytesToRead
parameter the response was over and you could close the socket. Unfortunately this isn't necessarily true.
This (lazy) sample solution lets the socket timeout after 10s and assumes that since the server has stopped sending information, all is well.
The correct way to do it, as Jon Skeet has already pointed out, is to parse the header, read the value of Content-Length
and then request the appropriate amount of data aftewards. This is slightly non-trivial, as you are dealing with a stream and there is no built-in way of magically parsing out the header nor its contents. It's far from difficult, though, and I will post a Github link once I get some decent looking code prepared.
The only other tweak left was to switch to https
to work with Mashape. This is as simple as replacing
await socket.ConnectAsync(hostname, "80");
with
await socket.ConnectAsync(hostname, "https", SocketProtectionLevel.Tls12);
Now we can handle "protocol violations" on Windows Phone/Store apps with no problems!
(Actually, if you are on a WinRT app, MessageWebSocket
seems like an even easier solution.)
Comments