| 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. |
public void Process() {
...
// Limit to local requests only
if (!_conn.IsLocal) {
_conn.WriteErrorAndClose(403);
return;
}
}
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:
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. private void CreateHost() {
_host =
(Host)ApplicationHost.CreateApplicationHost
(typeof(Host), _virtualPath, _physicalPath);
_host.Configure(this, _port, _virtualPath,
_physicalPath, _installPath);
}
Cassini is missing a key featurelogging. 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:
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. 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();
}
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:
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. 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
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. |
| DevX is a division of Internet.com. © Copyright 2010 Internet.com. All Rights Reserved. Legal Notices |