|
|
|
|
Creating your own Web Server using C#
By
Imtiaz Alam October 15, 2001
This article explains how to write a simple web server application using C#.
|
|
Technologies:
.NET 1.0/1.1,Visual C# .NET |
|
Total downloads : |
635 |
|
Total page views : |
39484 |
|
Rating : |
|
0/5 |
|
This article has been rated : |
0 times |
|
|
|
|
Download
Files:
|
|
|
|
|
|
|
|
|
|
Similar ArticlesMost ReadTop RatedLatest
|
|
Related EbooksTop Videos
|
|
|
Description
|
|
LINQ for Visual C# 2005 is a short yet comprehensive guide to the major features of LINQ. It thoroughly covers LINQ to Objects, LINQ to SQL, LINQ to DataSet, and LINQ to XML.
|
|
|
|
|
|
|
|
|
|
|
|
|
Environment: C#, .net
SUMMARY: This article explains how to write a simple web server application using C#. Though it can be developed in any .net supported language, I chose C# for this example. The code is compiled using beta2. Microsoft (R) Visual C# Compiler Version 7.00.9254 [CLR version v1.0.2914]. It can be used with Beta1 with some minor modification. This application can co-exists with IIS or any web server, the key is to choose any free port. I assume that the user has some basic understanding of .net and C# or VB.Net. This Web server just returns html formatted files and also supports images. It does not loads the embedded image or supports any kind of scripting. I have developed a console-based application for simplicity.
First we will define the root folder for our web server. Eg: C:\MyPersonalwebServer, and will create a Data directory underneath, our root directory Eg: C:\MyPersonalwebServer\Data. We will Create three files under data directory i.e.
- Mimes.Dat
- Vdirs.Dat
- Default.Dat
Mime.Dat will have the mime type supported by our web server. The format will be ; e.g.
.html; text/html .htm; text/html .bmp; image/bmp
VDirs.Dat will have the virtual directory Information. The format will be ; e.g. /; C:\myWebServerRoot/ test/; C:\myWebServerRoot\Imtiaz\
Default.Dat will have the virtual directory Information; e.g.
default.html default.htm Index.html Index.htm;
We will store all the information in plain text file for simplicity, we can use XML, registry or even hard code it. Before proceeding to our code let us first look the header information which browser will pass while requesting for our web site.
Let say we request for test.html we type http://localhost:5050/test.html (Remember to include port in the url), here what the web server gets..
GET /test.html HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */* Accept-Language: en-usAccept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 4.0; .NET CLR 1.0.2914) Host: localhost:5050Connection: Keep-Alive
Let us dive into the code...
// MyWebServer Written by Imtiaz Alam namespace Imtiaz { using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading ; class MyWebServer { private TcpListener myListener ; private int port = 5050 ; // Select any free port you wish //The constructor which make the TcpListener start listening on th //given port. It also calls a Thread on the method StartListen(). public MyWebServer() { try { //start listing on the given port myListener = new TcpListener(port) ; myListener.Start(); Console.WriteLine("Web Server Running... Press ^C to Stop..."); //start the thread which calls the method 'StartListen' Thread th = new Thread(new ThreadStart(StartListen)); th.Start() ; } catch(Exception e) { Console.WriteLine("An Exception Occurred while Listening :" +e.ToString()); } }
We defined namespace, included the references required in our application and initialized the port in the constructor, started the listener and created a new thread and called startlisten function.
Now let us assume that the user does not supplies the file name, in that case we have to identify the default filename and send to the browser, as in IIS we define the default document under documents tab.
We have already stored the default file name in the default.dat and stored in the data directory. The GetTheDefaultFileName function takes the directory path as input, open the default.dat file and looks for the file in the directory provided and returns the file name or blank depends on the situation.
public string GetTheDefaultFileName(string sLocalDirectory) { StreamReader sr; String sLine = ""; try { //Open the default.dat to find out the list // of default file sr = new StreamReader("data\\Default.Dat"); while ((sLine = sr.ReadLine()) != null) { //Look for the default file in the web server root folder if (File.Exists( sLocalDirectory + sLine) == true) break; } } catch(Exception e) { Console.WriteLine("An Exception Occurred : " + e.ToString()); } if (File.Exists( sLocalDirectory + sLine) == true) return sLine; else return ""; }
We also need to resolve the virtual directory to the actual physical directory like we do in IIS. We have already stored the mapping between the Actual and Virtual directory in Vdir.Dat. Remember in all the cases the file format is very important.
public string GetLocalPath(string sMyWebServerRoot, string sDirName) { StreamReader sr; String sLine = ""; String sVirtualDir = ""; String sRealDir = ""; int iStartPos = 0; //Remove extra spaces sDirName.Trim(); // Convert to lowercase sMyWebServerRoot = sMyWebServerRoot.ToLower(); // Convert to lowercase sDirName = sDirName.ToLower(); try { //Open the Vdirs.dat to find out the list virtual directories sr = new StreamReader("data\\VDirs.Dat"); while ((sLine = sr.ReadLine()) != null) { //Remove extra Spaces sLine.Trim(); if (sLine.Length > 0) { //find the separator iStartPos = sLine.IndexOf(";"); // Convert to lowercase sLine = sLine.ToLower(); sVirtualDir = sLine.Substring(0,iStartPos); sRealDir = sLine.Substring(iStartPos + 1); if (sVirtualDir == sDirName) { break; } } } } catch(Exception e) { Console.WriteLine("An Exception Occurred : " + e.ToString()); } if (sVirtualDir == sDirName) return sRealDir; else return ""; }
We also need to identify the Mime type, using the file extension supplied by the user
public string GetMimeType(string sRequestedFile) { StreamReader sr; String sLine = ""; String sMimeType = ""; String sFileExt = ""; String sMimeExt = ""; // Convert to lowercase sRequestedFile = sRequestedFile.ToLower(); int iStartPos = sRequestedFile.IndexOf("."); sFileExt = sRequestedFile.Substring(iStartPos); try { //Open the Vdirs.dat to find out the list virtual directories sr = new StreamReader("data\\Mime.Dat"); while ((sLine = sr.ReadLine()) != null) { sLine.Trim(); if (sLine.Length > 0) { //find the separator iStartPos = sLine.IndexOf(";"); // Convert to lower case sLine = sLine.ToLower(); sMimeExt = sLine.Substring(0,iStartPos); sMimeType = sLine.Substring(iStartPos + 1); if (sMimeExt == sFileExt) break; } } } catch (Exception e) { Console.WriteLine("An Exception Occurred : " + e.ToString()); } if (sMimeExt == sFileExt) return sMimeType; else return ""; }
Now we will write the function, to build and sends header information to the browser (client)
public void SendHeader(string sHttpVersion, string sMIMEHeader, int iTotBytes, string sStatusCode, ref Socket mySocket) { String sBuffer = ""; // if Mime type is not provided set default to text/html if (sMIMEHeader.Length == 0 ) { sMIMEHeader = "text/html"; // Default Mime Type is text/html } sBuffer = sBuffer + sHttpVersion + sStatusCode + "\r\n"; sBuffer = sBuffer + "Server: cx1193719-b\r\n"; sBuffer = sBuffer + "Content-Type: " + sMIMEHeader + "\r\n"; sBuffer = sBuffer + "Accept-Ranges: bytes\r\n"; sBuffer = sBuffer + "Content-Length: " + iTotBytes + "\r\n\r\n"; Byte[] bSendData = Encoding.ASCII.GetBytes(sBuffer); SendToBrowser( bSendData, ref mySocket); Console.WriteLine("Total Bytes : " + iTotBytes.ToString()); }
The SendToBrowser function sends information to the browser. This is an overloaded function.
public void SendToBrowser(String sData, ref Socket mySocket) { SendToBrowser (Encoding.ASCII.GetBytes(sData), ref mySocket); } public void SendToBrowser(Byte[] bSendData, ref Socket mySocket) { int numBytes = 0; try { if (mySocket.Connected) { if (( numBytes = mySocket.Send(bSendData, bSendData.Length,0)) == -1) Console.WriteLine("Socket Error cannot Send Packet"); else { Console.WriteLine("No. of bytes send {0}" , numBytes); } } else Console.WriteLine("Connection Dropped...."); } catch (Exception e) { Console.WriteLine("Error Occurred : {0} ", e ); } }
We now have all the building blocks ready, now we will delve into the key function of our application.
public void StartListen() { int iStartPos = 0; String sRequest; String sDirName; String sRequestedFile; String sErrorMessage; String sLocalDir; String sMyWebServerRoot = "C:\\MyWebServerRoot\\"; String sPhysicalFilePath = ""; String sFormattedMessage = ""; String sResponse = "";
while(true) { //Accept a new connection Socket mySocket = myListener.AcceptSocket() ; Console.WriteLine ("Socket Type " + mySocket.SocketType ); if(mySocket.Connected) { Console.WriteLine("\nClient Connected!!\n==================\n CLient IP {0}\n", mySocket.RemoteEndPoint) ; //make a byte array and receive data from the client Byte[] bReceive = new Byte[1024] ; int i = mySocket.Receive(bReceive,bReceive.Length,0) ; //Convert Byte to String string sBuffer = Encoding.ASCII.GetString(bReceive); //At present we will only deal with GET type if (sBuffer.Substring(0,3) != "GET" ) { Console.WriteLine("Only Get Method is supported.."); mySocket.Close(); return; } // Look for HTTP request iStartPos = sBuffer.IndexOf("HTTP",1); // Get the HTTP text and version e.g. it will return "HTTP/1.1" string sHttpVersion = sBuffer.Substring(iStartPos,8); // Extract the Requested Type and Requested file/directory sRequest = sBuffer.Substring(0,iStartPos - 1); //Replace backslash with Forward Slash, if Any sRequest.Replace("\\","/"); //If file name is not supplied add forward slash to indicate //that it is a directory and then we will look for the //default file name.. if ((sRequest.IndexOf(".") <1) && (!sRequest.EndsWith("/"))) { sRequest = sRequest + "/"; } //Extract the requested file name iStartPos = sRequest.LastIndexOf("/") + 1; sRequestedFile = sRequest.Substring(iStartPos); //Extract The directory Name sDirName = sRequest.Substring(sRequest.IndexOf("/"), sRequest.LastIndexOf("/")-3);
The code is self-explanatory. It receives the request, converts it into string from bytes then look for the request type, extracts the HTTP Version, file and directory information.
///////////////////////////////////////////////////////////////////// // Identify the Physical Directory ///////////////////////////////////////////////////////////////////// if ( sDirName == "/") sLocalDir = sMyWebServerRoot; else { //Get the Virtual Directory sLocalDir = GetLocalPath(sMyWebServerRoot, sDirName); } Console.WriteLine("Directory Requested : " + sLocalDir); //If the physical directory does not exists then // dispaly the error message if (sLocalDir.Length == 0 ) { sErrorMessage = "<H2>Error!! Requested Directory does not exists</H2><Br>"; //sErrorMessage = sErrorMessage + "Please check data\\Vdirs.Dat"; //Format The Message SendHeader(sHttpVersion, "", sErrorMessage.Length, " 404 Not Found", ref mySocket); //Send to the browser SendToBrowser(sErrorMessage, ref mySocket); mySocket.Close(); continue; }
Note: Microsoft Internet Explorer usually displays a 'friendy' HTTP Error Page if you want to display our error message then you need to disable the 'Show friendly HTTP error messages' option under the 'Advanced' tab in Tools->Internet Options. Next we look if the directory name is supplied, we call GetLocalPath function to get the physical directory information, if the directory not found (or does not mapped with entry in Vdir.Dat) error message is sent to the browser.. Next we will identify the file name, if the filename is not supplied by the user we will call the GetTheDefaultFileName function to retrieve the filename, if error occurred it is thrown to browser.
///////////////////////////////////////////////////////////////////// // Identify the File Name ///////////////////////////////////////////////////////////////////// //If The file name is not supplied then look in the default file list if (sRequestedFile.Length == 0 ) { // Get the default filename sRequestedFile = GetTheDefaultFileName(sLocalDir); if (sRequestedFile == "") { sErrorMessage = "<H2>Error!! No Default File Name Specified</H2>"; SendHeader(sHttpVersion, "", sErrorMessage.Length, " 404 Not Found", ref mySocket); SendToBrowser ( sErrorMessage, ref mySocket); mySocket.Close(); return; } }
Then we need to indentify the Mime type
///////////////////////////////////////////////////////////////////// // Get TheMime Type ///////////////////////////////////////////////////////////////////// String sMimeType = GetMimeType(sRequestedFile); //Build the physical path sPhysicalFilePath = sLocalDir + sRequestedFile; Console.WriteLine("File Requested : " + sPhysicalFilePath);
Now the final steps of opening the requested file and sending it to the browser..
if (File.Exists(sPhysicalFilePath) == false) { sErrorMessage = "<H2>404 Error! File Does Not Exists...</H2>"; SendHeader(sHttpVersion, "", sErrorMessage.Length, " 404 Not Found", ref mySocket); SendToBrowser( sErrorMessage, ref mySocket); Console.WriteLine(sFormattedMessage); } else { int iTotBytes=0; sResponse =""; FileStream fs = new FileStream(sPhysicalFilePath, FileMode.Open, FileAccess.Read, FileShare.Read); // Create a reader that can read bytes from the FileStream. BinaryReader reader = new BinaryReader(fs); byte[] bytes = new byte[fs.Length]; int read; while((read = reader.Read(bytes, 0, bytes.Length)) != 0) { // Read from the file and write the data to the network sResponse = sResponse + Encoding.ASCII.GetString(bytes,0,read); iTotBytes = iTotBytes + read; } reader.Close(); fs.Close(); SendHeader(sHttpVersion, sMimeType, iTotBytes, " 200 OK", ref mySocket); SendToBrowser(bytes, ref mySocket); //mySocket.Send(bytes, bytes.Length,0); } mySocket.Close(); } } } } }
Compilation and Execution
To compile the program from the command line:

In my version of .net I dont need to specify any library name, may be for old version we require to add the reference to dll, using /r parameter.
To run the application simply type the application name and press Enter..

Now, let say user send the request, our web server will identify the default file name and sends to the browser.

User can also request the Image file..

Possible Improvements
There are many improvements can be made to the WebServer application. Currently it does not supports embedded images and no supports for scripting. We can write our own Isapi filter for the same or we can also use the IIS isapi filter for our learning purpose. The code to write basic Isapi filter is very well explained at ISAPI Filters: Designing SiteSentry, an Anti-Scraping Filter for IIS
Conclusion
This article gives very basic idea of writing Web server application, lots of improvement can be done. Ill appreciate if I can get any comments on improving the same. I am also looking forward for adding the capabilities of calling Microsoft Isapi Filter from this application.
|
|
|
Login
to add your contents and source code to this article
|
|
|
|
|
|
|
|
|
|
|
|
Imtiaz Alam
Imtiaz Alam is a Senior Developer, currently residing in Phoenix, Arizona. He is an MCP since 1998, and has more than five years of development experience in developing Mirosoft based Solution.
|
|
|
|
|
|
|
|
|
C# Consulting is founded in 2002 by the founders of C# Corner. Unlike a traditional
consulting company, our consultants are well-known experts in .NET and many of them
are MVPs, authors, and trainers. We specialize in Microsoft .NET development and
utilize Agile Development and Extreme Programming practices to provide fast pace
quick turnaround results. Our software development model is a mix of Agile Development,
traditional SDLC, and Waterfall models.
|
|
Click here to learn more about C# Consulting. |
|
|
|
|
|
|
|
Introducing MaxV - one click. infinite control. Hyper-V Hosting from MaximumASP.
Finally – a virtual platform that delivers next-generation Windows Server 2008 Hyper-V virtualization technology from a managed hosting partner you can truly depend on. Visit www.maximumasp.com/max for a FREE 30 day trial. Hurry offer ends soon.
Climb aboard the MaxV platform and take advantage of High Availability, Intelligent Monitoring, Recurrent Backups, and Scalability – with no hassle or hidden fees.
As a managed hosting partner focused solely on Microsoft technologies since 2000, MaximumASP is uniquely qualified to provide the superior support that our business is built on. Unparalleled expertise with Microsoft technologies lead to working directly with Microsoft as first to offer IIS 7 and SQL 2008 betas in a hosted environment; partnering in the Go Live Program for Hyper-V; and product co-launches built on WS 2008 with Hyper-V technology.
|
Dynamic PDF
ceTE software specializes in components for dynamic PDF generation and manipulation. The DynamicPDF™ product line allows you to dynamically generate PDF documents, merge PDF documents and new content to existing PDF documents from within your applications.
|
Boost the performance of your .NET applications
“ANTS Profiler took us straight to the specific areas of our code which were the cause of our performance issues." Terry Phillips, Sr. Developer, Harley-Davidson Dealer Systems. Download your free trial of ANTS Profiler.
|
Go.NET
Build custom interactive diagrams, network, workflow editors, flowcharts, or software design tools. Includes many predefined kinds of nodes, links, and basic shapes. Supports layers, scrolling, zooming, selection, drag-and-drop, clipboard, in-place editing, tooltips, grids, printing, overview window, palette. 100% implemented in C# as a managed .NET Control. Document/View/Tool architecture with many properties&events. Optional automatic layout.
|
Dundas Software
Dundas Chart for .NET is the most advanced .NET charting package available today. With an extremely complete feature set, elegant architecture and easy implementation, Dundas Chart can quickly add advanced Charting functionality to enhance and transform ASP.NET and Windows Forms applications. Whether you are implementing charting into internal projects, or building applications for clients, Dundas Chart offers advanced technology and advanced results to get the most out of data.
|
|
|
|
|
|
|
|
|
Download
Files:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|