1 /******************************************************************************* 2 3 copyright: Copyright (c) Nov 2007 Kris Bell. All rights reserved 4 5 license: BSD style: $(LICENSE) 6 7 version: Nov 2007: Initial release 8 9 author: Kris 10 11 Support for HTTP chunked I/O. 12 13 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html 14 15 *******************************************************************************/ 16 17 module tango.net.http.ChunkStream; 18 19 private import tango.io.stream.Lines; 20 21 private import tango.io.device.Conduit, 22 tango.io.stream.Buffered; 23 24 private import Integer = tango.text.convert.Integer; 25 26 /******************************************************************************* 27 28 Prefix each block of data with its length (in hex digits) and add 29 appropriate \r\n sequences. To commit the stream you'll need to use 30 the terminate() function and optionally provide it with a callback 31 for writing trailing headers 32 33 *******************************************************************************/ 34 35 class ChunkOutput : OutputFilter 36 { 37 private OutputBuffer output; 38 39 /*********************************************************************** 40 41 Use a buffer belonging to our sibling, if one is available 42 43 ***********************************************************************/ 44 45 this (OutputStream stream) 46 { 47 super (output = BufferedOutput.create(stream)); 48 } 49 50 /*********************************************************************** 51 52 Write a chunk to the output, prefixed and postfixed in a 53 manner consistent with the HTTP chunked transfer coding 54 55 ***********************************************************************/ 56 57 final override size_t write (const(void)[] src) 58 { 59 char[8] tmp = void; 60 61 output.append (Integer.format (tmp, src.length, "x")) 62 .append ("\r\n") 63 .append (src) 64 .append ("\r\n"); 65 return src.length; 66 } 67 68 /*********************************************************************** 69 70 Write a zero length chunk, trailing headers and a terminating 71 blank line 72 73 ***********************************************************************/ 74 75 final void terminate (scope void delegate(OutputBuffer) headers = null) 76 { 77 output.append ("0\r\n"); 78 if (headers) 79 headers (output); 80 output.append ("\r\n"); 81 } 82 } 83 84 85 /******************************************************************************* 86 87 Parse hex digits, and use the resultant size to modulate requests 88 for incoming data. A chunk size of 0 terminates the stream, so to 89 read any trailing headers you'll need to provide a delegate handler 90 for receiving those 91 92 *******************************************************************************/ 93 94 class ChunkInput : Lines!(char) 95 { 96 private alias void delegate(const(char)[] line) Headers; 97 98 private Headers headers; 99 private uint available; 100 101 /*********************************************************************** 102 103 Prime the available chunk size by reading and parsing the 104 first available line 105 106 ***********************************************************************/ 107 108 this (InputStream stream, Headers headers = null) 109 { 110 set (stream); 111 this.headers = headers; 112 } 113 114 /*********************************************************************** 115 116 Reset ChunkInput to a new InputStream 117 118 ***********************************************************************/ 119 120 override ChunkInput set (InputStream stream) 121 { 122 super.set (stream); 123 available = nextChunk(); 124 return this; 125 } 126 127 /*********************************************************************** 128 129 Read content based on a previously parsed chunk size 130 131 ***********************************************************************/ 132 133 final override size_t read (void[] dst) 134 { 135 if (available is 0) 136 { 137 // terminated 0 - read headers and empty line, per rfc2616 138 const(char)[] line; 139 while ((line = super.next).length) 140 if (headers) 141 headers (line); 142 return IConduit.Eof; 143 } 144 145 auto size = dst.length > available ? available : dst.length; 146 auto read = super.read (dst [0 .. size]); 147 148 // check for next chunk header 149 if (read != IConduit.Eof && (available -= read) is 0) 150 { 151 // consume trailing \r\n 152 super.input.seek (2); 153 available = nextChunk (); 154 } 155 156 return read; 157 } 158 159 /*********************************************************************** 160 161 Read and parse another chunk size 162 163 ***********************************************************************/ 164 165 private final uint nextChunk () 166 { 167 const(char)[] tmp; 168 169 if ((tmp = super.next).ptr) 170 return cast(uint) Integer.parse (tmp, 16); 171 return 0; 172 } 173 } 174 175 176 /******************************************************************************* 177 178 *******************************************************************************/ 179 180 debug (ChunkStream) 181 { 182 import tango.io.Console; 183 import tango.io.device.Array; 184 185 void main() 186 { 187 auto buf = new Array(40); 188 auto chunk = new ChunkOutput (buf); 189 chunk.write ("hello world"); 190 chunk.terminate; 191 auto input = new ChunkInput (buf); 192 Cout.stream.copy (input); 193 } 194 }