A simple in-process HTTP server for Windows 8 Metro apps

Tuesday, September 18, 2012


Below is some code for a simple HTTP server for Metro/Modern-style Windows 8 apps. This code supports GET requests for a resource located at the root-level (e.g. http://localhost:8000/foo.txt) and looks for the corresponding file in the Data directory of the app package. This might be useful for unit testing, among other things.

public class HttpServer : IDisposable {
  private const uint BufferSize = 8192;
  private static readonly StorageFolder LocalFolder
               = Windows.ApplicationModel.Package.Current.InstalledLocation;

  private readonly StreamSocketListener listener;

  public HttpServer(int port) {
    this.listener = new StreamSocketListener();
    this.listener.ConnectionReceived += (s, e) => ProcessRequestAsync(e.Socket);
    this.listener.BindServiceNameAsync(port.ToString());
  }

  public void Dispose() {
    this.listener.Dispose();
  }

  private async void ProcessRequestAsync(StreamSocket socket) {
    // this works for text only
    StringBuilder request = new StringBuilder();
    using(IInputStream input = socket.InputStream) {
      byte[] data = new byte[BufferSize];
      IBuffer buffer = data.AsBuffer();
      uint dataRead = BufferSize;
      while (dataRead == BufferSize) {
        await input.ReadAsync(buffer, BufferSize, InputStreamOptions.Partial);
        request.Append(Encoding.UTF8.GetString(data, 0, data.Length));
        dataRead = buffer.Length;
      }
    }

    using (IOutputStream output = socket.OutputStream) {
      string requestMethod = request.ToString().Split('\n')[0];
      string[] requestParts = requestMethod.Split(' ');

      if (requestParts[0] == "GET")
        await WriteResponseAsync(requestParts[1], output);
      else
        throw new InvalidDataException("HTTP method not supported: "
                                       + requestParts[0]);
    }
  }

  private async Task WriteResponseAsync(string path, IOutputStream os) {
    using (Stream resp = os.AsStreamForWrite()) {
      bool exists = true;
      try {
        // Look in the Data subdirectory of the app package
        string filePath = "Data" + path.Replace('/', '\\');
        using (Stream fs = await LocalFolder.OpenStreamForReadAsync(filePath)) {
          string header = String.Format("HTTP/1.1 200 OK\r\n" +
                          "Content-Length: {0}\r\n" +
                          "Connection: close\r\n\r\n",
                          fs.Length);
          byte[] headerArray = Encoding.UTF8.GetBytes(header);
          await resp.WriteAsync(headerArray, 0, headerArray.Length);
          await fs.CopyToAsync(resp);
        }
      }
      catch (FileNotFoundException) {
        exists = false;
      }

      if (!exists) {
        byte[] headerArray = Encoding.UTF8.GetBytes(
                              "HTTP/1.1 404 Not Found\r\n" +
                              "Content-Length:0\r\n" +
                              "Connection: close\r\n\r\n");
        await resp.WriteAsync(headerArray, 0, headerArray.Length);
      }

      await resp.FlushAsync();
    }
  }
}

Usage:

const int port = 8000;
using (HttpServer server = new HttpServer(port)) {
  using (HttpClient client = new HttpClient()) {
    try {
      byte[] data = await client.GetByteArrayAsync(
                    "http://localhost:" + port + "/foo.txt");
      // do something with 
    }
    catch (HttpRequestException) {
      // possibly a 404
    }
  }
}

Notes:

  • The app manifest needs to declare the "Internet (Client & Server)" and/or the "Private Networks (Client & Server)" capability.
  • Due to the Windows 8 security model:
    • A Metro app can access network servers within its own process.
    • A Metro app Foo can access network servers hosted by another Metro app Bar iff Foo has a loopback exemption. You can use CheckNetIsolation.exe to do this. This is useful for debugging only, as it must be done manually.
    • A Desktop app cannot access network servers hosted by a Metro app. The BackgroundDownloader appears to fall into this category even though that is an API available to Metro apps.

Tags: win8, metro, http, server | Posted at 13:54 | Comments (16)


Comments

J on Tuesday, January 29, 2013 at 11:12

Nice article. So absolutely no way to use winrt app as http server?

David on Tuesday, January 29, 2013 at 17:35

You can, but not on the loopback interface. If you declare the capability in the app manifest, then it can listen for connections coming from hosts other than localhost.

I don't think you can listen on port 80 or other privileged ports, though.

Ant on Friday, March 22, 2013 at 13:26

Hi!

Why is it that the following doesn't render:

HttpServer server = new HttpServer(8000);

// WebView wv = new WebView(); the WebView is in the XAML instead.
wv.Source = "http://localhost:8000/foo.txt";

---------------
Yet this does work:

using (HttpClient client = new HttpClient())
{
try
{
byte[] data = await client.GetByteArrayAsync(
"http://localhost:" + port + "/foo.txt");
//string encoded = System.Convert.ToBase64String(data);
string a = System.Text.Encoding.UTF8.GetString(data, 0, data.Length);
wv.NavigateToString(a);
}
catch (HttpRequestException ex)
{
// possibly a 404
}
}

Ant on Friday, March 22, 2013 at 13:29

Note that the second example is wrapped by your server code,I forgot it:

const int port = 8000;
using (HttpServer server = new HttpServer(port))
{
// the above client code in here!
}

David on Friday, March 22, 2013 at 15:29

I'm not sure how the WebView works under the hood -- if it uses a different process to perform its downloading (which is reasonable), then the security restrictions mentioned above will prevent this from working, but HttpClient will work since it's in the same app process.

Have you tried ms-appdata://? http://msdn.microsoft.com/en-us/library/windows/apps/hh781215.aspx

Anthony on Tuesday, March 26, 2013 at 12:45

Hi David,

Thanks for your inspiration, I've created an Offline Http Server project at offline.codeplex.com, if you want to join as a developer (considering you inspired the project), please do.

You'll notice that you've been credited in the Documentation page!

At the time of writing this it handles multiple connections, accepts recursive paths, has over 400 mime types (overkill, I know, it was easier just to paste the whole list in and think about stripping stuff out later), etc. And the nice thing is that it easily streams .MP4 files over 500 MB :P

Please drop by!

Soernt on Monday, June 3, 2013 at 07:49

Hi David,

Thanks for sharing the code! I start using your code but it fails if the request is long enough. The basic problem within the code is the missing "message frameing". You need to evaluate the http headers to know how long (Content-Length) the request will be. The simple

while (dataRead == BufferSize) { ...}

is to simple.

Gavin Greenwalt on Tuesday, July 29, 2014 at 11:30

FYI here are all of the references you need to include:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.Networking.Sockets;

Vijaykumar R on Tuesday, July 14, 2015 at 06:23

Hi David,

I was implementing this HTTP server for one of my project and it seems to be working great. Is there a way that we can modify the http requests to https as we are using the webviews in WINJS apps ?

Can you help me modify this to handle https requests ? It will be a great help!!

David on Tuesday, July 14, 2015 at 12:06

This sample code wasn't meant to be used in a real app. I wrote it because I wanted to use it for unit tests. If you want to modify this code to handle HTTPS, you'll probably have to take an SSL/TLS library and implement the handshake and handle all the crypto yourself. This code here is simple because HTTP is a simple, text-based protocol by design, but encryption changes that.

Allan on Monday, December 7, 2015 at 00:48

Hi, I came across this website when doing some research for an app I might try to write:
- Listen to a port, say 8080, on my LAN using Wifi
- If it receives a GET request for a predetermined path (/startvid), it starts recording the video camera
- If it receives another GET (/stopvid) it stops recording

Use case:
- Phone mounted on a tripod, pointed at my cat's favorite chair next to my computer
- Cat occasionally stops by and does something worth videoing.
- I can *NOT* reach up and hit the record button on the phone when the cat decides to be photogenic, because my moving will spook the cat, and make him run away.
- When I decide the cat's behavior is worth recording, I pull up a web browser, and hit http://myphonesIP:8080/startvid from my PC. Obviously I use /stopvid to stop.

As far as I know, this use case - Remotely stopping/starting video on the phone - does not currently exist in the Windows store. There are apps which use the phone to control other cameras, but nothing that satisfies this use case.

Does this sound possible within Windows Phone 8.1's limitations? Based on what I've read here, I think it does. Thanks.

Phan on Monday, December 14, 2015 at 01:27

Hi David,

Thank you very much for your nice post. I use this in my project, and it works properly for GET request. But I couldn't get body data of POST request. I tried to read it from Socket InputStream, but I didn't get this data. Do you have any suggestion for me?

Thanks.

satanand on Thursday, January 28, 2016 at 01:58

AsBuffer() method not available . so what should i do behalf of that.

Ahmed on Thursday, March 10, 2016 at 07:11

Guys you should check out IotWeb if planning to use on real app Universal Windows Platform (UWP - One app, Raspberry PI, Phone, Desktop) or iOS and Android via Xamarin, or Mono (Mac, Linux) for

by Sensaura

http://sensaura.org/pages/tools/iotweb/index.html
https://github.com/sensaura-public/iotweb

which i don´t own our work for or know, but got to know of via the class

it primarily fullfledged

using System;
using System.Threading;
using IotWeb.Server;
using IotWeb.Common.Util;
using IotWeb.Common.Http;

// Set up and run the server
HttpServer server = new HttpServer();
server.AddHttpRequestHandler("/",
new HttpResourceHandler(
Utilities.GetContainingAssembly(typeof(Program)),
"Resources.Site",
"index.html" ));

server.AddWebSocketRequestHandler("/sockets/",
new WebSocketHandler());

server.Start(8000);

Console.WriteLine("Server running - press any key to stop.");
Console.ReadKey();


------------------------
/// <summary>
/// Simple 'echo' web socket server
/// </summary>
class WebSocketHandler : IWebSocketRequestHandler
{

public bool WillAcceptRequest(string uri, string protocol)
{
return (uri.Length == 0) && (protocol == "echo");
}

public void Connected(WebSocket socket)
{
socket.DataReceived += OnDataReceived;
}

void OnDataReceived(WebSocket socket, string frame)
{
socket.Send(frame);
}
}

Luis on Tuesday, March 22, 2016 at 10:23

Thx, works like a charm even on WinRT

Ajay on Wednesday, November 30, 2016 at 11:27

How can we extend this to have secure connections i.e. using HTTPS (SSL/TLS) does the SDK has the capability?

Add a comment

Name:
Email: (optional, not displayed to public)
URL:

Comment: