www.pudn.com > SharpDownload(FTP¼°WEBÏÂÔØ).zip > HTTPClient.cs


using System; 
using System.Net; 
using System.IO; 
using System.Text; 
using System.Net.Sockets; 
using System.Diagnostics; 
using System.Runtime.Remoting; 
using System.Runtime.Remoting.Messaging; 
 
#region HTTP info 
/* 
 *  
 * More info: http://www.w3.org/Protocols/rfc2616/rfc2616.html 
 
STATUS CODES 
  "100"  ; Section 10.1.1: Continue 
| "101"  ; Section 10.1.2: Switching Protocols 
| "200"  ; Section 10.2.1: OK 
| "201"  ; Section 10.2.2: Created 
| "202"  ; Section 10.2.3: Accepted 
| "203"  ; Section 10.2.4: Non-Authoritative Information 
| "204"  ; Section 10.2.5: No Content 
| "205"  ; Section 10.2.6: Reset Content 
| "206"  ; Section 10.2.7: Partial Content 
| "300"  ; Section 10.3.1: Multiple Choices 
| "301"  ; Section 10.3.2: Moved Permanently 
| "302"  ; Section 10.3.3: Found 
| "303"  ; Section 10.3.4: See Other 
| "304"  ; Section 10.3.5: Not Modified 
| "305"  ; Section 10.3.6: Use Proxy 
| "307"  ; Section 10.3.8: Temporary Redirect 
| "400"  ; Section 10.4.1: Bad Request 
| "401"  ; Section 10.4.2: Unauthorized 
| "402"  ; Section 10.4.3: Payment Required 
| "403"  ; Section 10.4.4: Forbidden 
| "404"  ; Section 10.4.5: Not Found 
| "405"  ; Section 10.4.6: Method Not Allowed 
| "406"  ; Section 10.4.7: Not Acceptable 
 
| "407"  ; Section 10.4.8: Proxy Authentication Required 
| "408"  ; Section 10.4.9: Request Time-out 
| "409"  ; Section 10.4.10: Conflict 
| "410"  ; Section 10.4.11: Gone 
| "411"  ; Section 10.4.12: Length Required 
| "412"  ; Section 10.4.13: Precondition Failed 
| "413"  ; Section 10.4.14: Request Entity Too Large 
| "414"  ; Section 10.4.15: Request-URI Too Large 
| "415"  ; Section 10.4.16: Unsupported Media Type 
| "416"  ; Section 10.4.17: Requested range not satisfiable 
| "417"  ; Section 10.4.18: Expectation Failed 
| "500"  ; Section 10.5.1: Internal Server Error 
| "501"  ; Section 10.5.2: Not Implemented 
| "502"  ; Section 10.5.3: Bad Gateway 
| "503"  ; Section 10.5.4: Service Unavailable 
| "504"  ; Section 10.5.5: Gateway Time-out 
| "505"  ; Section 10.5.6: HTTP Version not supported 
*/ 
#endregion 
 
namespace SharpDownload 
{ 
	///  
	/// HTTP-specific implementation of ProtocolClient. 
	/// Provides HTTP download capabilities. 
	///  
	public class HttpClient : ProtocolClient 
	{ 
		///  
		/// HTTP Exception class 
		///  
		public class HttpException : Exception 
		{ 
			public HttpException(string message) : base(message){} 
			public HttpException(string message, Exception innerException) : base(message,innerException){} 
		} 
		public class HttpRedirectException : ApplicationException 
		{ 
			private string newURL = string.Empty; 
			public HttpRedirectException(string URLRedirect) 
			{ 
				newURL = URLRedirect; 
			} 
			public string URL 
			{ 
				get 
				{ 
					return newURL; 
				} 
			} 
		} 
 
		private const string MODULE_NAME = "HttpClient"; 
		private const string CRLF = "\r\n";       // 0D0A 
		private FileDownload.FileDownloadCommandEnum command = FileDownload.FileDownloadCommandEnum.NoCommand; 
 
		///  
		/// Constructor 
		///  
		public HttpClient() 
		{ 
			this.Port = 80; 
		} 
		 
		///  
		/// Close the Http connection. 
		///  
		public override void Close() 
		{ 
			Debug.WriteLine("Closing connection to " + this.server, MODULE_NAME ); 
 
			if( this.clientSocket != null ) 
			{ 
				this.clientSocket.Close(); 
				this.loggedin = false; 
			} 
 
			this.cleanup(); 
		} 
 
		///  
		/// Overridden function returns if the server supports resume 
		///  
		/// true if server supports resume 
		public override bool CanResume() 
		{ 
			return CanResume("/"); 
		} 
 
		///  
		/// Overridden function returns if the server supports resume 
		///  
		/// true if server supports resume 
		public override bool CanResume(string remFileName) 
		{ 
			// Check if the server supports Range: command 
			// TODO: Need to fix this! 
			//return true; 
			if ( !this.loggedin ) this.Login(); 
 
			string header; 
			// Create a HEAD HTTP request for the file 
			header  = "HEAD /" + remFileName + " HTTP/1.1" + CRLF; 
			header += "Host: " + this.Server + CRLF; 
			header += "Range: bytes=0-0" + CRLF; 
			header += "Connection: close" + CRLF;	// NOTE: HTTP/1.1 automatically keeps connection open unles 'Connection: close' specified 
			header += CRLF;  // Header terminated with a blank line 
 
			this.sendCommand(header); 
 
			this.Close(); 
 
			if ( this.resultCode == 200 | this.resultCode == 206 ) 
			{ 
				Debug.WriteLine("Range supported", MODULE_NAME); 
				return true; 
			} 
			else 
			{ 
				Debug.WriteLine("Range supported", MODULE_NAME); 
				return false; 
			} 
		} 
 
		///  
		/// Return the size of a file. 
		///  
		///  
		///  
		public override long GetFileSize(string fileName) 
		{ 
			if ( !this.loggedin ) this.Login(); 
 
			string header; 
			string contentLengthHeader = "Content-Length: "; 
			string locationHeader = "Location: "; 
 
			// Create a HEAD HTTP request for the file 
			header  = "HEAD /" + fileName + " HTTP/1.1" + CRLF; 
			header += "Host: " + this.Server + CRLF; 
			header += "Connection: keep-alive" + CRLF;	// NOTE: HTTP/1.1 automatically keeps connection open unles 'Connection: close' specified 
			header += CRLF;  // Header terminated with a blank line 
 
			this.sendCommand(header); 
			long size=0; 
 
			switch (this.resultCode) 
			{ 
				case 200: 
					// Find content length line 
					foreach (string arrayVal in this.fullMessage) 
					{ 
						int index = arrayVal.IndexOf(contentLengthHeader, 0); 
						if (index!=-1) 
						{ 
							size = long.Parse(arrayVal.Substring(contentLengthHeader.Length, arrayVal.Length-contentLengthHeader.Length)); 
							break; 
						} 
					} 
					break; 
				case 302: 
					// Found returned from server - get filesize from supplied URI 
					string newLocationURI = string.Empty; 
					foreach (string arrayVal in this.fullMessage) 
					{ 
						int index = arrayVal.IndexOf(locationHeader, 0); 
						if (index!=-1) 
						{ 
							newLocationURI = arrayVal.Substring(locationHeader.Length, arrayVal.Length-locationHeader.Length); 
							break; 
						} 
					} 
					if (newLocationURI==string.Empty) throw new ApplicationException("HTTPClient ERROR: 302 returned, but couldn't find new URI in header!"); 
 
					Debug.WriteLine("302 Found - trying new URI (" + newLocationURI + ")", MODULE_NAME); 
					throw new HttpRedirectException(newLocationURI); 
					// Get the new URL info 
//					URLComponent url = new URLComponent(newLocationURI); 
//					this.Close(); 
//					this.Server = url.ServerAddress; 
//					this.RemotePath = url.Directory + "/" + url.FileName; 
//					size = this.GetFileSize(newLocationURI); 
				default: 
					throw new HttpException(this.result.Substring(4)); 
			} 
 
			this.Close(); 
			 
			Debug.WriteLine("File size is: " + size.ToString() + " bytes", MODULE_NAME); 
			return size; 
		} 
 
		///  
		/// Stops the current download 
		///  
		public override void StopDownload() 
		{ 
		} 
 
		///  
		/// Download a remote file to the Assembly's local directory, 
		/// keeping the same file name. 
		///  
		///  
		public override DownloadResult Download(string remFileName) 
		{ 
			return this.Download(remFileName,"",false); 
		} 
 
		///  
		/// Download a remote file to the Assembly's local directory, 
		/// keeping the same file name, and set the resume flag. 
		///  
		///  
		///  
		public override DownloadResult Download(string remFileName, Boolean resume) 
		{ 
			return this.Download(remFileName, "", resume); 
		} 
		 
		///  
		/// Download a remote file to a local file name which can include 
		/// a path. The local file name will be created or overwritten, 
		/// but the path must exist. 
		///  
		///  
		///  
		public override DownloadResult Download(string remFileName, string locFileName) 
		{ 
			return this.Download(remFileName, locFileName, false); 
		} 
 
 
		public override DownloadResult Download(string remFileName,string locFileName, Boolean resume) 
		{ 
			return this.Download(remFileName, locFileName, resume, 0); 
		} 
 
		public override DownloadResult Download(string remFileName,string locFileName, Boolean resume, long byteOffset) 
		{ 
			return this.Download(remFileName, locFileName, resume, byteOffset, 0); 
		} 
 
		public override DownloadResult Download(string remFileName,string locFileName, Boolean resume, long byteOffset, long byteStopDownload) 
		{ 
			return this.Download(remFileName, locFileName, resume, byteOffset, byteStopDownload, 0); 
		} 
 
		public override DownloadResult Download(string remFileName,string locFileName, Boolean resume, long byteOffset, long byteStopDownload, long totalFileSize) 
		{ 
			return this.Download(remFileName, locFileName, resume, byteOffset, byteStopDownload, totalFileSize, false); 
		} 
 
		///  
		/// Download a remote file to a local file name which can include 
		/// a path, and set the resume flag. The local file name will be 
		/// created or overwritten, but the path must exist. 
		///  
		/// Remote file name on the server 
		/// Local file name to save to 
		/// true attempts to resume the download 
		/// Offset to start download 
		/// Point to stop download for partial 
		/// The total size of the file 
		/// Is this the last part in the list? 
		public override DownloadResult Download(string remFileName, string locFileName, Boolean resume, long byteOffset, long byteStopDownload, long totalFileSize, bool isLastPart) 
		{ 
 
			bool foundStart = false; 
			bool stopPointReached = false; 
			long locFileOffset = 0; 
			string httpGetCommand = string.Empty; 
 
			DateTime timeout;	// Timeout value for loop 
 
			// Set mode to binary 
			//this.BinaryMode = true; 
 
			OnDownloadStartedEvent(new DownloadStartedEventArgs()); 
 
			// Get the file output stream 
			FileStream output = GetLocalFileStream(locFileName, remFileName); 
 
			// Check if resume requested 
			if ( resume ) 
			{ 
				// Set the byte offset for the local file to end of file 
				locFileOffset = output.Length; 
				if (locFileOffset>0) 
				{ 
					// Some data already received prevously 
					DataReceivedEventArgs eDataReceived = new DataReceivedEventArgs(output.Length); 
					OnDataReceivedEvent(eDataReceived); 
				} 
				if ( locFileOffset > 0 | byteOffset > 0 | byteStopDownload > 0) 
				{ 
					// Check already downloaded the part 
					if (locFileOffset>=totalFileSize && totalFileSize>0) 
					{ 
						// This part download has already been completed 
						Debug.WriteLine("Part download was completed previously (" + locFileOffset + " bytes already downloaded, part size " + totalFileSize + ")", MODULE_NAME); 
						output.Close(); 
						return DownloadResult.Finished; 
					} 
 
					// Create a HTTP GET request for the file range 
					httpGetCommand  = "GET /" + remFileName + " HTTP/1.1" + CRLF; 
					httpGetCommand += "Host: " + this.Server + CRLF; 
					 
					// Check if a stop download point set or if this is last part 
					long startByteRange = locFileOffset + byteOffset; 
					if (byteStopDownload==0 | isLastPart) 
					{ 
						// Get all remaining bytes 
						httpGetCommand += "Range: bytes=" + startByteRange + "-" + CRLF; 
					} 
					else 
					{ 
						// Get a range of bytes 
						httpGetCommand += "Range: bytes=" + startByteRange + "-" + (startByteRange + byteStopDownload) + CRLF; 
					} 
					httpGetCommand += "Connection: close" + CRLF; 
					httpGetCommand += CRLF;  // Header terminated with a blank line 
				} 
				output.Seek( locFileOffset, SeekOrigin.Begin ); 
 
			} 
			else 
			{ 
				// Create a default GET command for the whole file 
				// This command is used if the server doesn't support resume 
				httpGetCommand  = "GET /" + remFileName + " HTTP/1.1" + CRLF; 
				httpGetCommand += "Host: " + this.Server + CRLF; 
				httpGetCommand += "Connection: close" + CRLF; 
				httpGetCommand += CRLF;  // Header terminated with a blank line 
			} 
 
 
			// Create a data socket 
			Socket cSocket = createDataSocket(); 
 
			// Send the get command 
			Debug.WriteLine(httpGetCommand, MODULE_NAME); 
			Byte[] cmdBytes = Encoding.ASCII.GetBytes( ( httpGetCommand + "\r\n" ).ToCharArray() ); 
			cSocket.Send(cmdBytes, cmdBytes.Length, 0); 
 
			try 
			{ 
				do 
				{ 
					// Reset the timeout value for the next read 
					// NOTE: Need this so that each time a socket receive is successful, 
					//		 we wait an additional "timeout" value for the next receive 
					timeout = DateTime.Now.AddSeconds(this.timeoutSeconds); 
			 
					this.ResetBuffer(); 
					this.bytes = cSocket.Receive( this.buffer, this.buffer.Length, 0 ); 
 
					// Check if any data received 
					if ( this.bytes <= 0) 
					{ 
						// No data, so the file has finished downloading 
						Debug.WriteLine("No data received - assumed end of stream", MODULE_NAME); 
						stopPointReached = true; 
					} 
 
					// Found the start of the file yet? 
					if (!foundStart) 
					{ 
						this.message = ASCII.GetString( this.buffer, 0, this.bytes ); 
						this.resultCode = int.Parse( this.message.Substring(9,3) ); 
 
						// Check the HTTP result code 
						switch (this.resultCode) 
						{ 
							case 200 | 206: 
							{ 
								// Complete/partial file download 
								// Find blank line occurrence (/r/n/r/n) 
								Byte[] blankLine = ASCII.GetBytes("\r\n\r\n"); 
								for (int i=0; i=byteStopDownload)) 
						{ 
							Debug.WriteLine("Stop download point reached", MODULE_NAME); 
							// Write the data to the output file UP TO the stop download point 
							Byte[] smallBuffer = CopyToSmallArray(this.buffer, (int)(byteStopDownload-output.Length+1)); 
							output.Write(smallBuffer, 0, smallBuffer.Length); 
							stopPointReached = true; 
						} 
						else 
						{ 
							// Write the data to the output file 
							output.Write(this.buffer, 0, this.bytes); 
						} 
 
						// Raise a data received event 
						try 
						{ 
							DataReceivedEventArgs eDataReceived = new DataReceivedEventArgs(output.Length); 
							OnDataReceivedEvent(eDataReceived); 
						} 
						catch (Exception eDataReceivedError) 
						{ 
							// Ignore event raise errors 
							// NOTE: I got errors here when the event CONSUMER raises an error - why? 
							Debug.WriteLine("An error occurred raising the Data Received event. [" + eDataReceivedError.Message + "]", MODULE_NAME); 
						} 
 
					} 
 
					// Check if the stop point was reached 
					if (stopPointReached)  
					{ 
						Debug.WriteLine("Stop point reached, breaking...", MODULE_NAME); 
						break; 
					} 
 
					// Check if we want to stop the download 
					if (command!=FileDownload.FileDownloadCommandEnum.NoCommand) 
					{ 
						Debug.WriteLine("Download command received, breaking...", MODULE_NAME); 
						break; 
					} 
 
				} while ( timeout > DateTime.Now); 
			} 
			catch (Exception eDownload) 
			{ 
				Debug.WriteLine("An error occurred while download the file [" + eDownload.Message + "]", MODULE_NAME); 
				throw; 
			} 
 
			// Clean-up 
			output.Close(); 
 
			if ( cSocket.Connected ) cSocket.Close(); 
 
			Debug.WriteLine("Download completed, response code is: " + this.resultCode, MODULE_NAME); 
 
			// Check if result code is ok 
			// EXCEPT if we manually stopped at a certain point (byteStopDownload) 
			if( this.resultCode != 200 && this.resultCode != 206 && byteStopDownload==0) 
				throw new HttpException(this.result.Substring(4)); 
 
			// Check if we stopped because of a command 
			if (this.Command!=FileDownload.FileDownloadCommandEnum.NoCommand) 
			{ 
				// Command received! 
				Debug.WriteLine("Download stopped due to command: " + this.Command, MODULE_NAME); 
				return DownloadResult.Paused; 
			} 
 
			return DownloadResult.Finished; 
		} 
 
		///  
		/// Read the response from the server 
		///  
		private void readResponse() 
		{ 
			this.message = ""; 
			this.result = this.readLine(); 
 
			if ( this.result.Length > 9 ) 
				this.resultCode = int.Parse( this.result.Substring(9,3) ); 
			else 
				this.result = null; 
		} 
 
		///  
		/// Reads lines from the buffer and appends to the return message 
		///  
		///  
		private string readLine() 
		{ 
			while(true) 
			{ 
				this.bytes = clientSocket.Receive( this.buffer, this.buffer.Length, 0 ); 
				this.message += ASCII.GetString( this.buffer, 0, this.bytes ); 
 
				if ( this.bytes < this.buffer.Length ) 
				{ 
					break; 
				} 
			} 
 
			// Split return string into lines 
			string[] msg = this.message.Split(new char[] {'\r','\n'}); 
 
			// Make sure split worked 
			if (msg.Length==0) 
			{ 
				// No return data 
				this.message = string.Empty; 
			} 
			else 
			{ 
				// Response code should always be in first array element 
				this.message = msg[0]; 
			} 
 
			// Set full message value 
			this.fullMessage = msg; 
 
			// TODO: Is this line needed for http? 
			//if ( this.message.Length > 4 && !this.message.Substring(3,1).Equals(" ") ) return this.readLine(); 
 
			if ( this.verboseDebugging ) 
			{ 
				for(int i = 0; i < msg.Length - 1; i++) 
				{ 
					Debug.Write( msg[i], "HttpClient" ); 
				} 
			} 
 
			return message; 
		} 
 
		///  
		/// Sends a command to the server 
		///  
		///  
		private void sendCommand(String command) 
		{ 
			if ( this.verboseDebugging ) Debug.WriteLine(command, MODULE_NAME); 
 
			Byte[] cmdBytes = Encoding.ASCII.GetBytes( ( command + "\r\n" ).ToCharArray() ); 
			clientSocket.Send( cmdBytes, cmdBytes.Length, 0); 
			this.readResponse(); 
		} 
 
		///  
		/// Overridden login method 
		///  
		public override void Login() 
		{ 
			Socket socket = base.createDataSocket(); 
			if (socket!=null) 
			{ 
				this.clientSocket = socket; 
				this.loggedin = true; 
			} 
		} 
		 
		///  
		/// Always release those sockets. 
		///  
		private void cleanup() 
		{ 
			if ( this.clientSocket!=null ) 
			{ 
				this.clientSocket.Close(); 
				this.clientSocket = null; 
			} 
			this.loggedin = false; 
		} 
 
		public override FileDownload.FileDownloadCommandEnum Command 
		{ 
			get 
			{ 
				return command; 
			} 
			set 
			{ 
				command = value; 
			} 
		} 
 
		///  
		/// Destructor 
		///  
		~HttpClient() 
		{ 
			this.cleanup(); 
		} 
 
	} 
}