123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
|
/*******************************************************************************
copyright: Copyright (c) Nov 2007 Kris Bell. All rights reserved
license: BSD style: $(LICENSE)
version: Nov 2007: Initial release
author: Kris
Support for HTTP chunked I/O.
See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
*******************************************************************************/
module tango.net.http.ChunkStream;
private import tango.io.stream.Lines;
private import tango.io.device.Conduit,
tango.io.stream.Buffered;
private import Integer = tango.text.convert.Integer;
/*******************************************************************************
Prefix each block of data with its length (in hex digits) and add
appropriate \r\n sequences. To commit the stream you'll need to use
the terminate() function and optionally provide it with a callback
for writing trailing headers
*******************************************************************************/
class ChunkOutput : OutputFilter
{
private OutputBuffer output;
/***********************************************************************
Use a buffer belonging to our sibling, if one is available
***********************************************************************/
this (OutputStream stream)
{
super (output = BufferedOutput.create(stream));
}
/***********************************************************************
Write a chunk to the output, prefixed and postfixed in a
manner consistent with the HTTP chunked transfer coding
***********************************************************************/
final override size_t write (const(void)[] src)
{
char[8] tmp = void;
output.append (Integer.format (tmp, src.length, "x"))
.append ("\r\n")
.append (src)
.append ("\r\n");
return src.length;
}
/***********************************************************************
Write a zero length chunk, trailing headers and a terminating
blank line
***********************************************************************/
final void terminate (scope void delegate(OutputBuffer) headers = null)
{
output.append ("0\r\n");
if (headers)
headers (output);
output.append ("\r\n");
}
}
/*******************************************************************************
Parse hex digits, and use the resultant size to modulate requests
for incoming data. A chunk size of 0 terminates the stream, so to
read any trailing headers you'll need to provide a delegate handler
for receiving those
*******************************************************************************/
class ChunkInput : Lines!(char)
{
private alias void delegate(const(char)[] line) Headers;
private Headers headers;
private uint available;
/***********************************************************************
Prime the available chunk size by reading and parsing the
first available line
***********************************************************************/
this (InputStream stream, Headers headers = null)
{
set (stream);
this.headers = headers;
}
/***********************************************************************
Reset ChunkInput to a new InputStream
***********************************************************************/
override ChunkInput set (InputStream stream)
{
super.set (stream);
available = nextChunk();
return this;
}
/***********************************************************************
Read content based on a previously parsed chunk size
***********************************************************************/
final override size_t read (void[] dst)
{
if (available is 0)
{
// terminated 0 - read headers and empty line, per rfc2616
const(char)[] line;
while ((line = super.next).length)
if (headers)
headers (line);
return IConduit.Eof;
}
auto size = dst.length > available ? available : dst.length;
auto read = super.read (dst [0 .. size]);
// check for next chunk header
if (read != IConduit.Eof && (available -= read) is 0)
{
// consume trailing \r\n
super.input.seek (2);
available = nextChunk ();
}
return read;
}
/***********************************************************************
Read and parse another chunk size
***********************************************************************/
private final uint nextChunk ()
{
const(char)[] tmp;
if ((tmp = super.next).ptr)
return cast(uint) Integer.parse (tmp, 16);
return 0;
}
}
/*******************************************************************************
*******************************************************************************/
debug (ChunkStream)
{
import tango.io.Console;
import tango.io.device.Array;
void main()
{
auto buf = new Array(40);
auto chunk = new ChunkOutput (buf);
chunk.write ("hello world");
chunk.terminate;
auto input = new ChunkInput (buf);
Cout.stream.copy (input);
}
}
|