Explore and Extend the Cassini Web Server

Explore and Extend the Cassini Web Server

TTP services are a core function for supporting many current application development technologies, including Web services, ASP.NET applications, and other traditional Web applications. The traditional way to provide HTTP support for Web applications on the Windows platform has been to run IIS. IIS has a number of problems however, including a history of security problems and some significant management issues. A viable alternative to using IIS is Cassini, Microsoft’s source-available Web server platform written entirely in C#. Cassini supports ASP.NET and other basic functions such as directory browsing, and is compliant with HTTP 1.1. Cassini also demonstrates the power of the .NET Framework for building fully functional internet application servers. Even better, Cassini comes with source code enabling you to see the internal functions and modify it to suit your needs.

What You Need
You should have an understanding of the .NET Framework and C# or VB.NET. To build the sample project included with this article you need Visual Studio .NET and the .NET Framework SP1.

The basis for the Cassini implementation lies in the System.Web.Hosting namespace. This namespace contains the types that are core to the function of ASP.NET, and allow you to implement your own ASP.NET applications. Two of the important classes in this namespace are the ApplicationHost and the SimpleWorkerRequest. The ApplicationHost provides the ability to host ASP.NET applications outside of IIS. This is based on the ability of the ApplicationHost to create application domains that are used to process incoming ASP.NET requests. The SimpleWorkerRequest is derived from HttpWorkerRequest, and provides a simple implementation of the HttpWorkerRequest class to facilitate the processing of HTTP requests. The HttpRuntime class provides a ProcessRequest() method, taking the SimpleWorkerRequest as an argument. This is the core of ASP.NET request processing, and these classes are leveraged in the Cassini Web server.

You can use Cassini in a variety of ways. One that stands out is for ASP.NET development on machines that don’t have IIS installed. The Cassini technologies form the basis for the Web server support in Microsoft’s Web Matrix tool, which can run on Windows XP Home Edition software or other Windows systems (2000 or better) that don’t have IIS installed. Cassini is also a great fit for building a small client application that’s based on ASP.NET, such as a local browser-based training application. You can implement the Cassini Web server library in a custom GUI application that spawns a browser and processes HTTP requests locally. However, the software will not process remote requests as delivered?although you can change that in the Cassini source code if you like. As delivered, the following code prevents Cassini from responding to remote requests:

   public void Process() {      ...      // Limit to local requests only      if (!_conn.IsLocal) {         _conn.WriteErrorAndClose(403);         return;      }      }

The Request class of the Cassini server exposes a public Process() method, which validates the state of the connection as being local, and if not responds with a HTTP 403 error.

To best leverage Cassini as a learning and productivity tool, you can take advantage of the ability to extend its functionality. To demonstrate this, I will provide a brief technical overview of the Cassini source so you can see what’s under the hood. Then I will demonstrate a custom feature extension to Cassini that will log all incoming connections and will add an entry to a log file. You’ll review what’s involved in building and debugging the source and consider some other possible application extensions for Cassini.

Cassini Under the Hood
The Cassini Web server consists of a number of modules that together support the Web hosting functions. You’ll find the main functions that you’ll need to implement Cassini in your own application in the Cassini.Server class. The Server class exposes methods that let you start and stop the Web server. Instances of the Server object are created in the caller’s App Domain. The Server creates and configures the new App Domain to process HTTP requests with the System.Web.Hosting.ApplicationHost.CreateApplicationHost() method. The Server class also exposes properties that enable you to specify the physical path for the server files, the port number to be used for HTTP, and the root URL to identify the server. The Server class exposes additional public methods such as Start(), Stop(), and Restart()to support Server application management. By using the Start() method of the Server class constructor, you can implement a private member named CreateHost() as shown below:

   private void CreateHost() {        _host =          (Host)ApplicationHost.CreateApplicationHost         (typeof(Host), _virtualPath, _physicalPath);      _host.Configure(this, _port, _virtualPath,          _physicalPath,   _installPath);   }   

The preceding method implements Cassini.Host and its Configure() method to configure a server process. This method implements a delegate called _onSocketAccept, which spawns a thread to process a new socket connection using the private member OnSocketAccept(). This method establishes a connection using Cassini.Connection and processes the request.

Cassini is missing a key feature?logging. As delivered, the server maintains no logs for the incoming requests. But logging is a useful feature for monitoring the use of certain functions in an application, or the times of day that an application experiences the greatest load. This opens up the possibility of extending Cassini to implement this feature, leveraging the availability of the source and an understanding of the internal workings to create log entries when the server accepts new connections and initiates request processing.

Extending Cassini Functionality
To implement a logging feature in Cassini, first determine what type of activity you want to capture. To make it simple, you can start by logging every socket connection that is established and processed. To do that, you can add logging functionality to the OnSocketAccept() function referenced earlier. You’ll use the Connection object’s ProcessOneRequest() method, which in turn implements the Request object’s Process() method.

To add functionality to Cassini, create a module containing the new classes and then implement those classes in the Cassini source. Review the sample logging class in Listing 1. This class exposes a public method called CreateLogEntry(). This method implements uses a StreamWriter object to create a log file if one does not exist and then uses the StreamWriter.WriteLine() method to write a string value to the log file. Finally, it closes the StreamWriter. You can find the class in the module named CassiniLogger.cs in the sample code. To implement this functionality in Cassini, you’ll need to modify the Cassini source and modify the build script used to create the Cassini assemblies.

To log an entry for each incoming socket connection as described, change the OnSocketAccept method of the Host class as shown below:

   private void OnSocketAccept(Object acceptedSocket) {         Connection conn =  new Connection(this,          (Socket)acceptedSocket);         //***** Modification to Cassini source code *****         //Logging extension to track connections         //Use CassiniLogger class to write connection          //class data to log file            CassiniLogger cl = new CassiniLogger(            "c:\Cassini\Logs\CustomLog.txt");            //Create log entry to track remote IP for          //connection and indicator          //of a local or remote connection         cl.CreateLogEntry(conn.RemoteIP + ", " +             conn.IsLocal);         //***** End modification *****         conn.ProcessOneRequest();   }

As you can see, the code alterations instantiate an instance of the CassiniLogger class using the constructor to specify the location for the log file. The code then calls the CreateLogEntry() method concatenates the RemoteIP and IsLocal properties of the Connection object into a string passed as the input parameter. In other words, the additional code captures the requesting IP address and the value of the IsLocal property from the instance of the Connection object which the server creates when the OnSocketAccept() method executes. The code logs an entry every time the Cassini server receives a request to process.

Running Cassini with Extensions
Now that you’ve modified the code to support the new extensions you need to compile a new version of the application that includes the updates. For more on debugging the changes you’ve made to the Cassini source, see the section of this article titled Debugging Cassini. The Cassini source download includes a build script that uses the command line C# compiler (csc.exe) to build the source; but to add the new functionality to the application, you need to build it using the modified Host.cs class and the new CassiniLogger.cs module. To do that, copy the CassiniLogger.cs file to the Cassini directory and modify the build script, a file named build.bat, as follows:

   csc /t:library /r:System.dll /r:System.Web.dll       /debug /out:Cassini.dll    AssemblyInfo.cs ByteParser.cs ByteString.cs       Connection.cs Host.cs    Messages.cs Request.cs Server.cs CassiniLogger.cs

Modifying the build script adds the CassiniLogger.cs module to the build, and also uses the /debug switch to output debugging information that may be useful when you need to step through these changes to the code. Executing the modified build script creates the CassiniWebServer.exe application and builds and registers the Cassini type library in the Global Assembly Cache.

Debugging Cassini
The Cassini source and build script uses the command line C# compiler. As distributed it does not support using Visual Studio .NET solutions and projects to build and debug the code. The best option for debugging Cassini is to use the Attach Process feature in Visual Studio .NET.

To debug your changes, first run the modified build script for the Cassini assemblies, which will create the executable application and the type library. Then run the CassiniWebServer.exe application. After it’s running, open up Visual Studio .NET and select Debug Processes from the Tools menu. You’ll see the Processes dialog shown in Figure 1. Select the CassiniWebServer.exe process, and then click the Attach button. Doing that adds the process to the list view in the lower portion of the window, and enables the Break, Detach, and Terminate buttons on the dialog. When you click the Break button, the debugger breaks into the selected process, letting you see the code that is currently being executed. From there you can set breakpoints in the code and inspect variables.

Figure 1: Visual Studio .NET enables you to easily attach to a process and debug it directly. When debugging .NET applications that have debug symbols supported through .pdb files, the debugging will enable you to view the source code for the application if it is available.

Note that with Cassini, the easiest way to debug the Web server is to set a breakpoint in the main executable and then step into the code for the Cassini.Server instance. For the example in this article you’ll want to set a breakpoint in the Host.cs file on the private method OnSocketAccept(), since that’s where you implemented the extended functionality for request logging.

As the sample application demonstrates, you can easily extend and modify Cassini to suit your individual needs. The ability to see and modify the source makes it a great platform for learning about ASP.NET and the development of internet application servers in general. In addition, Cassini’s extensibility makes it an ideal choice for hosting your local client ASP.NET applications or for developing and testing ASP.NET applications.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist