req.SendFinalChunkedResponse();
Based on work done by others I rewrote the webserver project to enable it to send responses that are larger than Netduino's internal memory. To do this I modified the Request object to support chunked transfer encoding (see http://en.wikipedia....ansfer_encoding ).
In order to send a chunked response, the data should be split in "chunks" of a reasonable size. In almost all cases this is very ease: if you want to send an image that is far bigger than Netduino's memory, just readit in memory from the SD card in chunks of, say, 1024 bytes and send each chunk separately. Thus, Netduino's memory will not use more than 1024 bytes at the time while the complete immage is send.
Before the first chunk a "header"should be send (this will also initialize chunked transfer) and after the last chunk the response had to be finalized.
Here is my code of the enhanced Request:
using System;using System.Net.Sockets;using System.Text;using System.Net;using System.Threading;namespace WebSpace{ /// <summary> /// Holds information about a web request and is able to send chuncked responses /// </summary> public class Request : IDisposable { private string method; private string url; private Socket client; static char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7','8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; //convert a byte to a hex string public static string ToHexString(int i) { int b1 = i & 0x0F; int b2 = (i >> 4) & 0x0F ; int b3 = (i >> 8) & 0x0F; int b4 = (i >> 12) & 0x0F; if (b4 > 0) return "" + hexDigits[b4] + hexDigits[b3] + hexDigits[b2] + hexDigits[b1]; else if (b3 > 0) return "" + hexDigits[b3] + hexDigits[b2] + hexDigits[b1]; else if (b2 > 0) return "" + hexDigits[b2] + hexDigits[b1]; else return "" + hexDigits[b1]; } internal Request(Socket Client, char[] Data) { client = Client; ProcessRequest(Data); } // Request method public string Method { get { return method; } } // URL of request public string URL { get { return url; } } // Client IP address public IPAddress Client { get { IPEndPoint ip = client.RemoteEndPoint as IPEndPoint; if (ip != null) return ip.Address; return null; } } // Send a response back to the client public void SendResponse(string response, string type = "text/html") { if (client != null) { string header = "HTTP/1.1 200 OKrnContent-Type: " + type + "; charset=utf-8rnContent-Length: " + response.Length.ToString() + "rnConnection: closernrn"; client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None); client.Send(Encoding.UTF8.GetBytes(response), response.Length, SocketFlags.None); } } public void SendHeaderChunkedResponse(string type = "text/html") { if (client != null) { string header = "HTTP/1.1 200 OKrnContent-Type: " + type + "; charset=utf-8rnTransfer-Encoding: chunkedrnConnection: closernrn"; client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None); Thread.Sleep(10) ; } } public void SendNextChunkedResponse(string response) { if (client != null) { string hex = ToHexString(response.Length); client.Send(Encoding.UTF8.GetBytes(hex + "rn"), hex.Length+2, SocketFlags.None); client.Send(Encoding.UTF8.GetBytes(response + "rn"), response.Length+2, SocketFlags.None); Thread.Sleep(10) ; } } public void SendNextChunkedResponse(byte[] response, int length) { if (client != null) { string hex = ToHexString(length); client.Send(Encoding.UTF8.GetBytes(hex + "rn"), hex.Length + 2, SocketFlags.None); client.Send(response, length, SocketFlags.None); client.Send(Encoding.UTF8.GetBytes("rn"), 2, SocketFlags.None); Thread.Sleep(10); } } public void SendFinalChunkedResponse() { if (client != null) { client.Send(Encoding.UTF8.GetBytes("0rnrn"), 5, SocketFlags.None); Thread.Sleep(10); } } public void Send404() { string header = "HTTP/1.1 404 Not FoundrnContent-Length: 0rnConnection: closernrn"; if (client != null) { client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None); } } private void ProcessRequest(char[] data) { string content = new string(data); string firstLine = content.Substring(0, content.IndexOf('n')); // Parse the first line of the request: "GET /path/ HTTP/1.1" string[] words = firstLine.Split(' '); method = words[0]; url = words[1]; // Could look for any further headers in other lines of the request if required (e.g. User-Agent, Cookie) } #region IDisposable Members public void Dispose() { if (client != null) { client.Close(); client = null; } } #endregion }}
Here is the code of the Listener:
using System;using System.Net.Sockets;using System.Net;using System.Text;using System.Threading;namespace WebSpace{ public delegate void RequestReceivedDelegate(Request request); public class Listener : IDisposable { const int maxRequestSize = 1024; readonly int portNumber = 80; private Socket listeningSocket = null; private RequestReceivedDelegate requestReceived; public Listener(RequestReceivedDelegate RequestReceived) : this(RequestReceived, 80) { } public Listener(RequestReceivedDelegate RequestReceived, int PortNumber) { portNumber = PortNumber; requestReceived = RequestReceived; listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listeningSocket.Bind(new IPEndPoint(IPAddress.Any, portNumber)); listeningSocket.Listen(10); new Thread(StartListening).Start(); } ~Listener() { Dispose(); } public void StartListening() { while (true) { try { using (Socket clientSocket = listeningSocket.Accept()) { IPEndPoint clientIP = clientSocket.RemoteEndPoint as IPEndPoint; // Debug.Print("Received request from " + clientIP.ToString()); var x = clientSocket.RemoteEndPoint; int availableBytes = clientSocket.Available; // Debug.Print(DateTime.Now.ToString() + " " + availableBytes.ToString() + " request bytes available"); int bytesReceived = (availableBytes > maxRequestSize ? maxRequestSize : availableBytes); if (bytesReceived > 0) { byte[] buffer = new byte[bytesReceived]; // Buffer probably should be larger than this. int readByteCount = clientSocket.Receive(buffer, bytesReceived, SocketFlags.None); using (Request r = new Request(clientSocket, Encoding.UTF8.GetChars(buffer))) { // Debug.Print(DateTime.Now.ToString() + " " + r.URL); if (requestReceived != null) requestReceived(r); } } } } catch (Exception e) { Thread.Sleep(10); Program.Log("!!! ERROR !!! " + e.Message + "n" + e.StackTrace); } } } #region IDisposable Members public void Dispose() { if (listeningSocket != null) listeningSocket.Close(); } #endregion }}
And finaly, here is some example code to send a large XML stream. Note the following three calls:
req.SendHeaderChunkedResponse("text/xml");
req.SendNextChunkedResponse(chunk, length);
req.SendFinalChunkedResponse();
An example to send the contents of a large files in simple XML as <data> ..... </data>
private const int CHUNK_SIZE = 4096; private static void List(Request req, string file) { byte[] chunk = new byte[CHUNK_SIZE + 256]; int len = 0; req.SendHeaderChunkedResponse("text/xml"); len = add(chunk, len, "<?xml version='1.0'?>n<data>n"); using (var sr = new StreamReader(file)) { String line; while ((line = sr.ReadLine()) != null) { if (len > CHUNK_SIZE) { req.SendNextChunkedResponse(chunk, len); len = 0; } if (sr.EndOfStream) break; } } len = add(chunk, len, "</data>n"); req.SendNextChunkedResponse(chunk, len); len = 0; req.SendFinalChunkedResponse(); } private static int add(byte[] chunk, int len, string msg) { for (int i = 0; i < msg.Length; i++) { chunk[ptr++] = (byte)msg.ToCharArray(i, 1)[0]; } return len; }