September 2012 1 post
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.