- // ==++==
- //
- //
- // Copyright (c) 2002 Microsoft Corporation. All rights reserved.
- //
- // The use and distribution terms for this software are contained in the file
- // named license.txt, which can be found in the root of this distribution.
- // By using this software in any fashion, you are agreeing to be bound by the
- // terms of this license.
- //
- // You must not remove this notice, or any other, from this software.
- //
- //
- // ==--==
- //============================================================
- //
- // File: HttpStreams.cs
- //
- // Summary: Defines streams used by HTTP channels
- //
- //============================================================
- using System;
- using System.Collections;
- using System.IO;
- using System.Net;
- using System.Net.Sockets;
- using System.Runtime.Remoting.Channels;
- using System.Text;
- using System.Threading;
- namespace System.Runtime.Remoting.Channels.Http
- {
- internal abstract class HttpServerResponseStream : Stream
- {
- public override bool CanRead { get { return false; } }
- public override bool CanSeek { get { return false; } }
- public override bool CanWrite { get { return true; } }
- public override long Length { get { throw new NotSupportedException(); } }
- public override long Position
- {
- get { throw new NotSupportedException(); }
- set { throw new NotSupportedException(); }
- }
- public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
- public override void SetLength(long value) { throw new NotSupportedException(); }
- public override int Read(byte[] buffer, int offset, int count)
- {
- throw new NotSupportedException();
- }
- } // HttpServerResponseStream
- internal class HttpFixedLengthResponseStream : HttpServerResponseStream
- {
- private Stream _outputStream = null; // funnel http data into here
- private static int _length;
- internal HttpFixedLengthResponseStream(Stream outputStream, int length)
- {
- _outputStream = outputStream;
- _length = length;
- } // HttpFixedLengthResponseStream
- public override void Close()
- {
- _outputStream.Flush();
- } // Close
- public override void Flush()
- {
- _outputStream.Flush();
- } // Flush
- private int min(int a, int b) { return a < b ? a : b;}
- public override void Write(byte[] buffer, int offset, int count)
- {
- _outputStream.Write(buffer, offset, count);
- } // Write
- public override void WriteByte(byte value)
- {
- _outputStream.WriteByte(value);
- } // WriteByte
- } // class HttpFixedLengthResponseStream
- internal class HttpChunkedResponseStream : HttpServerResponseStream
- {
- private static byte[] _trailer = Encoding.ASCII.GetBytes("0/r/n/r/n"); // 0-length, no trailer, end chunked
- private static byte[] _endChunk = Encoding.ASCII.GetBytes("/r/n");
- private Stream _outputStream = null; // funnel chunked http data into here
- private byte[] _chunk; // chunk of data to write
- private int _chunkSize; // size of chunk
- private int _chunkOffset; // next byte to write in to chunk
- private byte[] _byteBuffer = new byte[1]; // buffer for writing bytes
- internal HttpChunkedResponseStream(Stream outputStream)
- {
- _outputStream = outputStream;
- _chunk = CoreChannel.BufferPool.GetBuffer();
- _chunkSize = _chunk.Length - 2; // reserve space for _endChunk directly in buffer
- _chunkOffset = 0;
- // write end chunk bytes at end of buffer (avoids extra socket write)
- _chunk[_chunkSize - 2] = (byte)'/r';
- _chunk[_chunkSize - 1] = (byte)'/n';
- } // HttpChunkedResponseStream
- public override void Close()
- {
- if (_chunkOffset > 0)
- FlushChunk();
- _outputStream.Write(_trailer, 0, _trailer.Length);
- _outputStream.Flush();
- CoreChannel.BufferPool.ReturnBuffer(_chunk);
- _chunk = null;
- } // Close
- public override void Flush()
- {
- if (_chunkOffset > 0)
- FlushChunk();
- _outputStream.Flush();
- } // Flush
- private int min(int a, int b) { return a < b ? a : b;}
- public override void Write(byte[] buffer, int offset, int count)
- {
- while (count > 0)
- {
- if ((_chunkOffset == 0) && (count >= _chunkSize))
- {
- // just write the rest as a chunk directly to the wire
- WriteChunk(buffer, offset, count);
- break;
- }
- else
- {
- // write bytes to current chunk buffer
- int writeCount = min(_chunkSize - _chunkOffset, count);
- Array.Copy(buffer, offset, _chunk, _chunkOffset, writeCount);
- _chunkOffset += writeCount;
- count -= writeCount;
- offset += writeCount;
- // see if we need to terminate the chunk
- if (_chunkOffset == _chunkSize)
- FlushChunk();
- }
- }
- } // Write
- public override void WriteByte(byte value)
- {
- _byteBuffer[0] = value;
- Write(_byteBuffer, 0, 1);
- } // WriteByte
- private void FlushChunk()
- {
- WriteChunk(_chunk, 0, _chunkOffset);
- _chunkOffset = 0;
- }
- private void WriteChunk(byte[] buffer, int offset, int count)
- {
- byte[] size = IntToHexChars(count);
- _outputStream.Write(size, 0, size.Length);
- if (buffer == _chunk)
- {
- // _chunk already has end chunk encoding at end
- _outputStream.Write(_chunk, offset, count + 2);
- }
- else
- {
- _outputStream.Write(buffer, offset, count);
- _outputStream.Write(_endChunk, 0, _endChunk.Length);
- }
- } // WriteChunk
- private byte[] IntToHexChars(int i)
- {
- String str = "";
- while (i > 0)
- {
- int val = i % 16;
- switch (val)
- {
- case 15: str = 'F' + str; break;
- case 14: str = 'E' + str; break;
- case 13: str = 'D' + str; break;
- case 12: str = 'C' + str; break;
- case 11: str = 'B' + str; break;
- case 10: str = 'A' + str; break;
- default: str = (char)(val + (int)'0') + str; break;
- }
- i = i / 16;
- }
- str += "/r/n";
- return Encoding.ASCII.GetBytes(str);
- } // IntToHexChars
- } // HttpChunkedResponseStream
- internal abstract class HttpReadingStream : Stream
- {
- public virtual bool ReadToEnd()
- {
- // This will never be called at a point where it is valid
- // for someone to use the remaining data, so we don't
- // need to buffer it.
- byte[] buffer = new byte[16];
- int bytesRead = 0;
- do
- {
- bytesRead = Read(buffer, 0, 16);
- } while (bytesRead > 0);
- return bytesRead == 0;
- }
- public virtual bool FoundEnd { get { return false; } }
- public override bool CanRead { get { return true; } }
- public override bool CanSeek { get { return false; } }
- public override bool CanWrite { get { return false; } }
- public override long Length { get { throw new NotSupportedException(); } }
- public override long Position
- {
- get{ throw new NotSupportedException(); }
- set{ throw new NotSupportedException(); }
- }
- public override void Flush() { throw new NotSupportedException(); }
- public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
- public override void SetLength(long value) { throw new NotSupportedException(); }
- public override void Write(byte[] buffer, int offset, int count)
- {
- throw new NotSupportedException();
- }
- } // HttpReadingStream
- internal class HttpFixedLengthReadingStream : HttpReadingStream
- {
- private HttpSocketHandler _inputStream = null; // read content data from here
- private int _bytesLeft; // bytes left in current chunk
- internal HttpFixedLengthReadingStream(HttpSocketHandler inputStream, int contentLength)
- {
- _inputStream = inputStream;
- _bytesLeft = contentLength;
- } // HttpFixedLengthReadingStream
- public override bool FoundEnd { get { return _bytesLeft == 0; } }
- public override void Close() {}
- private int min(int a, int b) { return a < b ? a : b;}
- public override int Read(byte[] buffer, int offset, int count)
- {
- if (_bytesLeft == 0)
- return 0;
- int readCount = _inputStream.Read(buffer, offset, min(_bytesLeft, count));
- if (readCount > 0)
- _bytesLeft -= readCount;
- return readCount;
- } // Read
- public override int ReadByte()
- {
- if (_bytesLeft == 0)
- return -1;
- _bytesLeft -= 1;
- return _inputStream.ReadByte();
- } // ReadByte
- } // HttpFixedLengthReadingStream
- // Stream class to read chunked data for HTTP
- // (assumes that provided outputStream will be positioned for
- // reading the body)
- internal class HttpChunkedReadingStream : HttpReadingStream
- {
- private static byte[] _trailer = Encoding.ASCII.GetBytes("0/r/n/r/n/r/n"); // 0-length, null trailer, end chunked
- private static byte[] _endChunk = Encoding.ASCII.GetBytes("/r/n");
- private HttpSocketHandler _inputStream = null; // read chunked http data from here
- private int _bytesLeft; // bytes left in current chunk
- private bool _bFoundEnd = false; // has end of stream been reached?
- private byte[] _byteBuffer = new byte[1]; // buffer for reading bytes
- internal HttpChunkedReadingStream(HttpSocketHandler inputStream)
- {
- _inputStream = inputStream;
- _bytesLeft = 0;
- } // HttpChunkedReadingStream
- public override bool FoundEnd { get { return _bFoundEnd; } }
- public override void Close()
- {
- } // Close
- private int min(int a, int b) { return a < b ? a : b;}
- public override int Read(byte[] buffer, int offset, int count)
- {
- int bytesRead = 0;
- while (!_bFoundEnd && (count > 0))
- {
- // see if we need to start reading a new chunk
- if (_bytesLeft == 0)
- {
- // this loop stops when the end of line is found
- for (;;)
- {
- byte b = (byte)_inputStream.ReadByte();
- // see if this is the end of the length
- if (b == '/r')
- {
- // This had better be '/n'
- if ((char)_inputStream.ReadByte() != '/n')
- {
- throw new RemotingException(
- CoreChannel.GetResourceString(
- "Remoting_Http_ChunkedEncodingError"));
- }
- else
- break; // we've finished reading the length
- }
- else
- {
- int value = HttpChannelHelper.CharacterHexDigitToDecimal(b);
- // make sure value is a hex-digit
- if ((value < 0) || (value > 15))
- {
- throw new RemotingException(
- CoreChannel.GetResourceString(
- "Remoting_Http_ChunkedEncodingError"));
- }
- // update _bytesLeft value to account for new digit on the right
- _bytesLeft = (_bytesLeft * 16) + value;
- }
- }
- if (_bytesLeft == 0)
- {
- // read off trailing headers and end-line
- String trailerHeader;
- do
- {
- trailerHeader = _inputStream.ReadToEndOfLine();
- } while (!(trailerHeader.Length == 0));
- _bFoundEnd = true;
- }
- }
- if (!_bFoundEnd)
- {
- int readCount = min(_bytesLeft, count);
- int bytesReadThisTime = _inputStream.Read(buffer, offset, readCount);
- if (bytesReadThisTime <= 0)
- {
- throw new RemotingException(
- CoreChannel.GetResourceString(
- "Remoting_Http_ChunkedEncodingError"));
- }
- _bytesLeft -= bytesReadThisTime;
- count -= bytesReadThisTime;
- offset += bytesReadThisTime;
- bytesRead += bytesReadThisTime;
- // see if the end of the chunk was found
- if (_bytesLeft == 0)
- {
- // read off "/r/n"
- char ch = (char)_inputStream.ReadByte();
- if (ch != '/r')
- {
- throw new RemotingException(
- CoreChannel.GetResourceString(
- "Remoting_Http_ChunkedEncodingError"));
- }
- ch = (char)_inputStream.ReadByte();
- if (ch != '/n')
- {
- throw new RemotingException(
- CoreChannel.GetResourceString(
- "Remoting_Http_ChunkedEncodingError"));
- }
- }
- }
- } // while (count > 0)
- return bytesRead;
- } // Read
- public override int ReadByte()
- {
- int readCount = Read(_byteBuffer, 0, 1);
- if (readCount == 0)
- return -1;
- return _byteBuffer[0];
- } // ReadByte
- } // class HttpChunkedReadingStream
- [Serializable]
- internal enum HttpVersion
- {
- V1_0,
- V1_1
- } // HttpVersion
- // Maintains control of a socket connection.
- internal class HttpServerSocketHandler : HttpSocketHandler
- {
- // Used to make sure verb characters are valid
- private static ValidateByteDelegate s_validateVerbDelegate =
- new ValidateByteDelegate(HttpServerSocketHandler.ValidateVerbCharacter);
- // Used to keep track of socket connections
- private static Int64 _connectionIdCounter = 0;
- // primed buffer data
- private static byte[] _bufferhttpContinue = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue/r/n/r/n");
- // stream manager data
- private HttpReadingStream _requestStream = null; // request stream we handed out.
- private HttpServerResponseStream _responseStream = null; // response stream we handed out.
- private Int64 _connectionId; // id for this connection
- // request state flags
- private HttpVersion _version; // http version used by client
- private int _contentLength = 0; // Content-Length value if found
- private bool _chunkedEncoding = false; // does request stream use chunked encoding?
- private bool _keepAlive = false; // does the client want to keep the connection alive?
- private bool _allowChunkedResponse = false; // can we send a chunked response?
- internal HttpServerSocketHandler(Socket socket, RequestQueue requestQueue) : base(socket, requestQueue)
- {
- _connectionId = Interlocked.Increment(ref _connectionIdCounter);
- } // HttpServerSocketHandler
- public bool KeepAlive { get { return _keepAlive; } }
- // FUTURE: Implement a response stream class that will only switch into chunked
- // mode once was the response grows beyond a certain size. Chunking is
- // disabled for now since without the custom stream, it degrades performance
- // for small calls where chunking is not needed.
- public bool AllowChunkedResponse { get { return false && _allowChunkedResponse; } }
- // Determine if it's possible to service another request
- public bool CanServiceAnotherRequest()
- {
- if (_keepAlive && (_requestStream != null))
- {
- if (_requestStream.FoundEnd || _requestStream.ReadToEnd())
- return true;
- }
- return false;
- } // CanServiceAnotherRequest
- // Prepare for reading a new request off of the same socket
- protected override void PrepareForNewMessage()
- {
- _requestStream = null;
- _responseStream = null;
- _contentLength = 0;
- _chunkedEncoding = false;
- _keepAlive = false;
- _allowChunkedResponse = false;
- } // PrepareForNewRequest
- protected override void SendErrorMessageIfPossible(Exception e)
- {
- // If we haven't started sending a response back, we can do the following.
- if ((_responseStream == null) && !(e is SocketException))
- {
- SendResponse(null, "500", "Internal Server Error", null);
- }
- } // SendErrorMessageIfPossible
- private static bool ValidateVerbCharacter(byte b)
- {
- if (Char.IsLetter((char)b) ||
- (b == '-'))
- {
- return true;
- }
- return false;
- } // ValidateVerbCharacter
- // read headers
- public BaseTransportHeaders ReadHeaders()
- {
- bool bSendContinue = false;
- BaseTransportHeaders headers = new BaseTransportHeaders();
- // read first line
- String verb, requestURI, version;
- ReadFirstLine(out verb, out requestURI, out version);
- if ((verb == null) || (requestURI == null) || (version == null))
- {
- throw new RemotingException(
- CoreChannel.GetResourceString(
- "Remoting_Http_UnableToReadFirstLine"));
- }
- if (version.Equals("HTTP/1.1")) // most common case
- _version = HttpVersion.V1_1;
- else
- if (version.Equals("HTTP/1.0"))
- _version = HttpVersion.V1_0;
- else
- _version = HttpVersion.V1_1; // (assume it will understand 1.1)
- if (_version == HttpVersion.V1_1)
- {
- _allowChunkedResponse = true;
- _keepAlive = true;
- }
- else // it's a 1.0 client
- {
- _allowChunkedResponse = false;
- _keepAlive = false;
- }
- // update request uri to be sure that it has no channel data
- String channelURI;
- String objectURI;
- channelURI = HttpChannelHelper.ParseURL(requestURI, out objectURI);
- if (channelURI == null)
- {
- objectURI = requestURI;
- }
- headers["__RequestVerb"] = verb;
- headers.RequestUri = objectURI;
- headers["__HttpVersion"] = version;
- // check to see if we must send continue
- if ((_version == HttpVersion.V1_1) &
- (verb.Equals("POST") || verb.Equals("PUT")))
- {
- bSendContinue = true;
- }
- ReadToEndOfHeaders(headers, out _chunkedEncoding, out _contentLength,
- ref _keepAlive, ref bSendContinue);
- if (bSendContinue && (_version != HttpVersion.V1_0))
- SendContinue();
- // add IP address and Connection Id to headers
- headers[CommonTransportKeys.IPAddress] = ((IPEndPoint)NetSocket.RemoteEndPoint).Address;
- headers[CommonTransportKeys.ConnectionId] = _connectionId;
- return headers;
- } // ReadHeaders
- public Stream GetRequestStream()
- {
- if (_chunkedEncoding)
- _requestStream = new HttpChunkedReadingStream(this);
- else
- _requestStream = new HttpFixedLengthReadingStream(this, _contentLength);
- return _requestStream;
- } // GetRequestStream
- public Stream GetResponseStream(String statusCode, String reasonPhrase,
- ITransportHeaders headers)
- {
- bool contentLengthPresent = false;
- bool useChunkedEncoding = false;
- int contentLength = 0;
- // check for custom user status code and reason phrase
- Object userStatusCode = headers["__HttpStatusCode"]; // someone might have stored an int
- String userReasonPhrase = headers["__HttpReasonPhrase"] as String;
- if (userStatusCode != null)
- statusCode = userStatusCode.ToString();
- if (userReasonPhrase != null)
- reasonPhrase = userReasonPhrase;
- // see if we can handle any more requests on this socket
- if (!CanServiceAnotherRequest())
- {
- headers["Connection"] = "Close";
- }
- // check for content length
- Object contentLengthEntry = headers["Content-Length"];
- if (contentLengthEntry != null)
- {
- contentLengthPresent = true;
- if (contentLengthEntry is int)
- contentLength = (int)contentLengthEntry;
- else
- contentLength = Convert.ToInt32(contentLengthEntry);
- }
- // see if we are going to use chunked-encoding
- useChunkedEncoding = AllowChunkedResponse && !contentLengthPresent;
- if (useChunkedEncoding)
- headers["Transfer-Encoding"] = "chunked";
- // write headers to stream
- ChunkedMemoryStream headerStream = new ChunkedMemoryStream(CoreChannel.BufferPool);
- WriteResponseFirstLine(statusCode, reasonPhrase, headerStream);
- WriteHeaders(headers, headerStream);
- headerStream.WriteTo(NetStream);
- headerStream.Close();
- // return stream ready for content
- if (useChunkedEncoding)
- _responseStream = new HttpChunkedResponseStream(NetStream);
- else
- _responseStream = new HttpFixedLengthResponseStream(NetStream, contentLength);
- return _responseStream;
- } // GetResponseStream
- private bool ReadFirstLine(out String verb, out String requestURI, out String version)
- {
- verb = null;
- requestURI = null;
- version = null;
- verb = ReadToChar(' ', s_validateVerbDelegate);
- byte[] requestUriBytes = ReadToByte((byte)' ');
- int decodedUriLength;
- HttpChannelHelper.DecodeUriInPlace(requestUriBytes, out decodedUriLength);
- requestURI = Encoding.UTF8.GetString(requestUriBytes, 0, decodedUriLength);
- version = ReadToEndOfLine();
- return true;
- } // ReadFirstLine
- private void SendContinue()
- {
- // Output:
- // HTTP/1.1 100 Continue
- // Send the continue response back to the client
- NetStream.Write(_bufferhttpContinue, 0, _bufferhttpContinue.Length);
- } // SendContinue
- public void SendResponse(Stream httpContentStream,
- String statusCode, String reasonPhrase,
- ITransportHeaders headers)
- {
- if (_responseStream != null)
- {
- _responseStream.Close();
- if (_responseStream != httpContentStream)
- {
- throw new RemotingException(
- "Http transport sink was not given the stream that it returned from GetResponseStream().");
- }
- // we are done with the response stream
- _responseStream = null;
- }
- else
- {
- if (headers == null)
- headers = new TransportHeaders();
- String serverHeader = (String)headers["Server"];
- if (serverHeader != null)
- serverHeader = HttpServerTransportSink.ServerHeader + ", " + serverHeader;
- else
- serverHeader = HttpServerTransportSink.ServerHeader;
- headers["Server"] = serverHeader;
- // Add length to response headers if necessary
- if (!AllowChunkedResponse && (httpContentStream != null))
- headers["Content-Length"] = httpContentStream.Length.ToString();
- else
- if (httpContentStream == null)
- headers["Content-Length"] = "0";
- GetResponseStream(statusCode, reasonPhrase, headers);
- // write HTTP content
- if(httpContentStream != null)
- {
- StreamHelper.CopyStream(httpContentStream, _responseStream);
- _responseStream.Close();
- httpContentStream.Close();
- }
- // we are done with the response stream
- _responseStream = null;
- }
- } // SendResponse
- } // class HttpServerSocketHandler
- } // namespace System.Runtime.Remoting.Channels.Http
HttpStreams.cs
最新推荐文章于 2024-01-24 22:33:04 发布