/// <summary> /// Closes the stream attached to this listener context. /// </summary> public void Close(int lingerValue) { try { // Close the underlying stream if (m_clientOutputStream != null) { try { if (m_clientOutputStream.m_Socket != null) { m_clientOutputStream.m_Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, lingerValue); } } catch {} m_clientOutputStream.Dispose(); m_clientOutputStream = null; } if (m_clientInputStream != null) { m_clientInputStream.Dispose(); m_clientInputStream = null; } } catch { } }
public Stream CloneStream() { InputNetworkStreamWrapper clone = this.MemberwiseClone() as InputNetworkStreamWrapper; clone.m_isClone = true; return(clone); }
/// <summary> /// Constructs a HttpListenerRequest is created by HttpListenerContext. /// </summary> /// <param name="clientStream">Network stream to the client.</param> /// <param name="maxHeaderLen">TBD</param> internal HttpListenerRequest(InputNetworkStreamWrapper clientStream, int maxHeaderLen) { this.m_clientStream = clientStream; // maxHeaderLen is in kilobytes (Desktop designer decided so). If -1 just maximum integer value this.m_maxResponseHeadersLen = maxHeaderLen == -1 ? 0x7FFFFFFF : maxHeaderLen * 1024; // If not set, default for content length is -1 this.m_contentLength = -1; }
/// <summary> /// Closes a response stream, if present. /// </summary> /// <param name="disposing">Not used.</param> protected override void Dispose(bool disposing) { if (m_responseStream != null) { bool closeConnection = true; if (m_httpWebRequest.KeepAlive) { string connValue = null; // Check if server have sent use "Connection:Close" if (m_httpResponseHeaders != null) { connValue = m_httpResponseHeaders[HttpKnownHeaderNames.Connection]; } // If server had not send this header or value is not "close", then we keep connection. closeConnection = connValue == null || connValue.ToLower() == HttpKnownHeaderValues.close; // Add new socket and port used to connect to the list of sockets. // Save connected socket and Destination IP End Point, so it can be used next time. // But first we need to validate that this socket is already not in the list. We do not want same socket to be twice in the list. lock (HttpWebRequest.m_ConnectedStreams) { // If it is not in the list - Add it if (closeConnection) { if (HttpWebRequest.m_ConnectedStreams.Contains(m_responseStream)) { // Either KeepAlive was not set or server requested closing connection. HttpWebRequest.m_ConnectedStreams.Remove(m_responseStream); } // Closing connection socket. m_responseStream.Dispose(); } else { m_responseStream.ReleaseStream(); } } } // Set flag that we already completed work on this stream. m_responseStream = null; } }
/// <summary> /// Closes the stream attached to this listener context. /// </summary> public void Close(int lingerValue) { try { if (this.m_clientOutputStream != null) { try { if (this.m_clientOutputStream.m_Socket != null) { this.m_clientOutputStream.m_Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, lingerValue); } } catch {} } if (this.m_ResponseToClient != null) { this.m_ResponseToClient.Close(); this.m_ResponseToClient = null; } // Close the underlying stream if (this.m_clientOutputStream != null) { this.m_clientOutputStream.Dispose(); this.m_clientOutputStream = null; } if (this.m_clientInputStream != null) { this.m_clientInputStream.Dispose(); this.m_clientInputStream = null; } } catch { } }
/// <summary> /// Internal constructor, used each time client connects. /// </summary> /// <param name="clientStream">The stream that is connected to the client. A stream is needed, to /// provide information about the connected client. /// See also the <see cref="System.Net.HttpListenerRequest"/> class. /// </param> /// <param name="httpListener">TBD</param> internal HttpListenerContext(OutputNetworkStreamWrapper clientStream, HttpListener httpListener) { // Saves the stream. m_clientOutputStream = clientStream; // Input stream does not own socket. m_clientInputStream = new InputNetworkStreamWrapper(clientStream.m_Stream, clientStream.m_Socket, false, null); // Constructs request and response classes. m_ClientRequest = new HttpListenerRequest(m_clientInputStream, httpListener.m_maxResponseHeadersLen); // Closing reponse to client causes removal from clientSocketsList. // Thus we need to pass clientSocketsList to client response. m_ResponseToClient = new HttpListenerResponse(m_clientOutputStream, httpListener); // There is incoming connection HTTP connection. Add new Socket to the list of connected sockets // The socket is removed from this array after correponding HttpListenerResponse is closed. httpListener.AddClientStream(m_clientOutputStream); // Set flag that HTTP request was not parsed yet. // It will be parsed on first access to m_ClientRequest or m_ResponseToClient m_IsHTTPRequestParsed = false; }
/// <summary> /// Constructs a HttpListenerRequest is created by HttpListenerContext. /// </summary> /// <param name="clientStream">Network stream to the client.</param> /// <param name="maxHeaderLen">TBD</param> internal HttpListenerRequest(InputNetworkStreamWrapper clientStream, int maxHeaderLen) { m_clientStream = clientStream; // maxHeaderLen is in kilobytes (Desktop designer decided so). If -1 just maximum integer value m_maxResponseHeadersLen = maxHeaderLen == -1 ? 0x7FFFFFFF : maxHeaderLen * 1024; // If not set, default for content length is -1 m_contentLength = -1; }
/// <summary> /// Sets the response stream. /// </summary> /// <param name="stream"></param> /// <remarks> /// Used internally during creation of HttpWebResponse. /// </remarks> internal void SetResponseStream(InputNetworkStreamWrapper stream) { m_responseStream = stream; }
/// <summary> /// Reads and parses HTTP response from server. /// After return of function HTTP response is read. /// </summary> /// <param name="inStream">Network stream connected to server.</param> /// <param name="defaultKeepAlive">TBD</param> /// <returns>CoreResponseData that describes server response.</returns> private CoreResponseData ParseHTTPResponse(InputNetworkStreamWrapper inStream, bool defaultKeepAlive) { // CoreResponseData keeps all the information of the response. CoreResponseData ret = new CoreResponseData(); // maximumHeadersLength is maximum total length of http header. Basically this is amount // of memory used for headers. int headersLength = m_maxResponseHeadersLen == -1 ? 0x7FFFFFFF : m_maxResponseHeadersLen * 1024; ret.m_shouldClose = !defaultKeepAlive; // Parse the request line. string line = inStream.Read_HTTP_Line(maxHTTPLineLength).Trim(); // Cutoff white spaces int currentOffset = 0; for (; currentOffset < line.Length && ' ' != line[currentOffset]; ++currentOffset) ; // find HTTP version, read http/1.x string httpVersionString = line.Substring(0, currentOffset).ToLower(); if (httpVersionString.Equals("http/1.1")) { ret.m_version = HttpVersion.Version11; } else if (httpVersionString.Equals("http/1.0")) { ret.m_version = HttpVersion.Version10; } else { ret.m_status = WebExceptionStatus.ServerProtocolViolation; ret.m_exceptionMessage = "Unknown http version: " + httpVersionString; return ret; } //advance to the status code for (; currentOffset < line.Length && ' ' == line[currentOffset]; ++currentOffset) ; // Read the status code int codeStart = currentOffset; for (; currentOffset < line.Length && ' ' != line[currentOffset]; ++currentOffset) ; int statusCode = -1; try { string statusCodeStr = line.Substring(codeStart, currentOffset - codeStart); statusCode = Convert.ToInt32(statusCodeStr); } catch (Exception e) { ret.m_status = WebExceptionStatus.ServerProtocolViolation; ret.m_exceptionMessage = "Missing status code in HTTP reply"; ret.m_innerException = e; return ret; } // If we get here - status code should be read. ret.m_statusCode = statusCode; // Advance to the status message. The message is optional for (; currentOffset < line.Length && ' ' != line[currentOffset]; ++currentOffset) ; ret.m_statusDescription = line.Substring(currentOffset); ret.m_headers = new WebHeaderCollection(true); ret.m_chunked = false; ret.m_contentLength = -1; while ((line = inStream.Read_HTTP_Header(maxHTTPLineLength)).Length > 0) { // line.Length is used for the header. Substruct it. headersLength -= line.Length; // If total length used for header is exceeded, we break if (headersLength < 0) { ret.m_status = WebExceptionStatus.ServerProtocolViolation; ret.m_exceptionMessage = "Headers size exceed limit"; return ret; } // Now parse the header. int sepIdx = line.IndexOf(':'); if (sepIdx == -1) { ret.m_status = WebExceptionStatus.ServerProtocolViolation; ret.m_exceptionMessage = "Illegal header format: " + line; return ret; } string headerName = line.Substring(0, sepIdx); string headerValue = line.Substring(sepIdx + 1).TrimStart(null); string matchableHeaderName = headerName.ToLower(); ret.m_headers.AddInternal(headerName, headerValue); if (matchableHeaderName.Equals("content-length")) { try { ret.m_contentLength = Convert.ToInt32(headerValue); // set the response stream length for the input stream, so that an EOF will be read // if the caller tries to read base the response content length inStream.m_BytesLeftInResponse = ret.m_contentLength; } catch (Exception e) { ret.m_status = WebExceptionStatus.ServerProtocolViolation; ret.m_exceptionMessage = "Content length NAN: " + headerValue; ret.m_innerException = e; return ret; } } else if (matchableHeaderName.Equals("transfer-encoding")) { if (headerValue.ToLower().IndexOf("chunked") != -1) { ret.m_chunked = true; } } else if (matchableHeaderName.Equals("connection")) { if (headerValue.ToLower().IndexOf(HttpKnownHeaderValues.close) != -1) { ret.m_shouldClose = true; } } } return ret; }
/// <summary> /// Submits request to the WEB server. /// </summary> private void SubmitRequest() { // We have connected socket. Create request stream // If proxy is set - connect to proxy server. if (m_proxy == null) { // Direct connection to target server. m_requestStream = EstablishConnection(m_originalUrl, m_originalUrl); } else // Connection through proxy. We create network stream connected to proxy { Uri proxyUri = m_proxy.GetProxy(m_originalUrl); if (m_originalUrl.Scheme == "https") { // For HTTPs we still need to know the target name to decide on persistent connection. m_requestStream = EstablishConnection(proxyUri, m_originalUrl); } else { // For normal HTTP all requests go to proxy m_requestStream = EstablishConnection(proxyUri, proxyUri); } } // We have connected stream. Set the timeout from HttpWebRequest m_requestStream.WriteTimeout = m_readWriteTimeout; m_requestStream.ReadTimeout = m_readWriteTimeout; // Now we need to write headers. First we update headers. PrepareHeaders(); // Now send request string and headers. byte[] dataToSend = GetHTTPRequestData(); #if DEBUG // In debug mode print the request. It helps a lot to troubleshoot the issues. int byteUsed, charUsed; bool completed = false; char[] charBuf = new char[dataToSend.Length]; UTF8decoder.Convert(dataToSend, 0, dataToSend.Length, charBuf, 0, charBuf.Length, true, out byteUsed, out charUsed, out completed); string strSend = new string(charBuf); Debug.Print(strSend); #endif // Writes this data to the network stream. m_requestStream.Write(dataToSend, 0, dataToSend.Length); m_requestSent = true; }
/// <summary> /// Returns network stream connected to server. It could be a proxy or a /// real server Uri. /// </summary> /// <param name="proxyServer">Uri that describes the proxy server.</param> /// <param name="targetServer">Uri that describes the target (real) server.</param> /// <returns>Nerwork stream connected to server.</returns> private InputNetworkStreamWrapper EstablishConnection(Uri proxyServer, Uri targetServer) { InputNetworkStreamWrapper retStream = null; // Create a socket and set reuse true. // But before creating new socket we look in the list of existing sockets. If socket for this host already // exist - use it. No need to create new socket. string remoteServer = targetServer.Host + ":" + targetServer.Port; lock (m_ConnectedStreams) { ArrayList removeStreamList = new ArrayList(); for (int i = 0; i < m_ConnectedStreams.Count; i++) { InputNetworkStreamWrapper inputStream = (InputNetworkStreamWrapper)m_ConnectedStreams[i]; if (inputStream.m_rmAddrAndPort == remoteServer && !inputStream.m_InUse) { // Re-use the connected socket. // But first we need to know that socket is not closed. try { // If socket is closed ( from this or other side ) the call throws exception. if (inputStream.m_Socket.Poll(1, SelectMode.SelectWrite)) { // No exception, good we can condtinue and re-use connected stream. // Control flow returning here means persistent connection actually works. inputStream.m_InUse = true; inputStream.m_lastUsed = DateTime.Now; retStream = inputStream; break; } else { removeStreamList.Add(inputStream); } } catch (Exception) { removeStreamList.Add(inputStream); } } } for (int i = 0; i < removeStreamList.Count; i++) { InputNetworkStreamWrapper removeStream = (InputNetworkStreamWrapper)removeStreamList[i]; // Means socket was closed. Remove it from the list. m_ConnectedStreams.Remove(removeStream); removeStream.Dispose(); } } if (retStream == null) { // Existing connection did not worked. Need to establish new one. IPAddress address = null; UriHostNameType hostNameType = proxyServer.HostNameType; if (hostNameType == UriHostNameType.IPv4) { address = IPAddress.Parse(proxyServer.Host); } else if (hostNameType == UriHostNameType.Dns) { IPHostEntry hostEntry = null; try { hostEntry = Dns.GetHostEntry(proxyServer.Host); } catch(SocketException se) { throw new WebException("host not available", se, WebExceptionStatus.ConnectFailure, null); } int addressListSize = hostEntry.AddressList.Length; for (int i = 0; i < addressListSize; i++) { if ((address = hostEntry.AddressList[i]) != null) { break; } } if (address == null) { throw new WebException("Unable to resolve Dns entry to valid IPv4 Address", WebExceptionStatus.NameResolutionFailure); } } else { throw new WebException("Only IPv4 or Dns host names allowed."); } // If socket was not found in waiting connections, then we create new one. Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); // Connect to remote endpoint try { IPEndPoint remoteEP = new IPEndPoint(address, proxyServer.Port); socket.Connect((EndPoint)remoteEP); } catch (SocketException e) { throw new WebException("connection failed", e, WebExceptionStatus.ConnectFailure, null); } bool isHttps = m_originalUrl.Scheme == "https"; // We have connected socket. Create request stream retStream = new InputNetworkStreamWrapper(new NetworkStream(socket), socket, !isHttps, proxyServer.Host + ":" + proxyServer.Port); // For https proxy works differenly from http. if (isHttps) { // If proxy is set, then for https we need to send "CONNECT" command to proxy. // Once this command is send, the socket from proxy works as if it is the socket to the destination server. if (proxyServer != targetServer) { String request = "CONNECT " + remoteServer + " HTTP/" + ProtocolVersion + "\r\n\r\n"; Byte[] bytesToSend = Encoding.UTF8.GetBytes(request); retStream.Write(bytesToSend, 0, bytesToSend.Length); // Now proxy should respond with the connected status. If it is successul, then we are good to go. CoreResponseData respData = ParseHTTPResponse(retStream, m_keepAlive); if (respData.m_statusCode != (int)HttpStatusCode.OK) { throw new WebException("Proxy returned " + respData.m_statusCode, WebExceptionStatus.ConnectFailure); } } // Once connection estiblished need to create secure stream and authenticate server. SslStream sslStream = new SslStream(retStream.m_Socket); // Throws exception is fails. sslStream.AuthenticateAsClient(m_originalUrl.Host, null, m_caCerts, m_caCerts != null ? SslVerification.CertificateRequired : SslVerification.NoVerification, SslProtocols.Default); // Changes the stream to SSL stream. retStream.m_Stream = sslStream; // Changes the address. Originally socket was connected to proxy, now as if it connected to m_originalUrl.Host on m_originalUrl.Port retStream.m_rmAddrAndPort = m_originalUrl.Host + ":" + m_originalUrl.Port; } lock (m_ConnectedStreams) { m_ConnectedStreams.Add(retStream); // if the current stream list is empty then start the timer that drops unused connections. if (m_ConnectedStreams.Count == 1) { m_DropOldConnectionsTimer.Change(HttpListener.DefaultKeepAliveMilliseconds, System.Threading.Timeout.Infinite); } } } return retStream; }
/// <summary> /// Removes the given stream from the connection pool /// </summary> internal static void RemoveStreamFromPool(InputNetworkStreamWrapper stream) { lock (m_ConnectedStreams) { if (m_ConnectedStreams.Contains(stream)) { m_ConnectedStreams.Remove(stream); } } }
/// <summary> /// Closes the stream attached to this listener context. /// </summary> public void Close(int lingerValue) { try { // Close the underlying stream if (m_clientOutputStream != null) { m_clientOutputStream.m_Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, lingerValue); m_clientOutputStream.Dispose(); m_clientOutputStream = null; } if (m_clientInputStream != null) { m_clientInputStream.Dispose(); m_clientInputStream = null; } } catch { } }
/// <summary> /// Closes a response stream, if present. /// </summary> /// <param name="disposing">Not used.</param> protected override void Dispose(bool disposing) { if (m_responseStream != null) { bool closeConnection = true; if (m_httpWebRequest.KeepAlive) { string connValue = null; // Check if server have sent use "Connection:Close" if (m_httpResponseHeaders != null) connValue = m_httpResponseHeaders[HttpKnownHeaderNames.Connection]; // If server had not send this header or value is not "close", then we keep connection. closeConnection = connValue == null || connValue.ToLower() == HttpKnownHeaderValues.close; // Add new socket and port used to connect to the list of sockets. // Save connected socket and Destination IP End Point, so it can be used next time. // But first we need to validate that this socket is already not in the list. We do not want same socket to be twice in the list. lock (HttpWebRequest.m_ConnectedStreams) { // If it is not in the list - Add it if (closeConnection) { if (HttpWebRequest.m_ConnectedStreams.Contains(m_responseStream)) { // Either KeepAlive was not set or server requested closing connection. HttpWebRequest.m_ConnectedStreams.Remove(m_responseStream); } // Closing connection socket. m_responseStream.Dispose(); } else { m_responseStream.ReleaseStream(); } } } // Set flag that we already completed work on this stream. m_responseStream = null; } }