Web request monitoring for .net - c#

Problem Background
I have few web applications which were developed using Microsoft .net framework 4.0 and running on the IIS 7.0 .so now i need to track traffic information(requested user,ip,application name ,address,request time etc..) for all these application and persist into a database or text file etc.
while i was searching on this i found HTTP handlers concept.
Is this handy to use my case ? or are there any alternatives which can fulfill my requirement?
I need to write this component as plug-abbe one.because i need connect this component easily to another web application also.
appreciate your ideas

Use an HttpModule to intercept all requests via the BeginRequest event, like this:
public class RequestLoggingModule : IHttpModule
{
private HttpApplication httpApp;
public void Init(HttpApplication httpApp)
{
this.httpApp = httpApp;
httpApp.BeginRequest += new EventHandler(OnBeginRequest);
}
void OnBeginRequest(object sender, EventArgs e)
{
// Get the user that made the request
HttpApplication application = (HttpApplication)sender;
HttpResponse response = application.Context.Response;
WindowsIdentity identity =
(WindowsIdentity)application.Context.User.Identity;
LogInformation(identity.Name);
// Do this for other information you want to log here
}
private void LogInformation(string data)
{
EventLog log = new EventLog();
log.Source = "Application XYZ Request Logging";
log.WriteEntry(data, EventLogEntryType.Information);
}
public void Dispose()
{
}
}

Related

Can I Terminate HTTP transaction on server WITHOUT sending response to client?

I'm writing a public-facing transaction processor. Naturally, we run on https:// and the payload carries all relevant detail so we'll only process legitimate transactions. However, as a public interface, any number of nefarious actors will no doubt be throwing shade at my server if for no other reason than to just be annoying.
When I detect such a request, is there anyway I can terminate processing at my end - not going to waste time on the transaction - but NOT send a response to the client? Basically, I'd like to force the nefarious clients into a timeout situation so that, if nothing else, it diminishes their capacity to annoy my server.
Here's the code:
public class Webhook : IHttpModule
{
/// <summary>
/// You will need to configure this module in the Web.config file of your
/// web and register it with IIS before being able to use it. For more information
/// see the following link: http://go.microsoft.com/?linkid=8101007
/// </summary>
private bool m_sslRequired = false;
#region IHttpModule Members
<snip...>
#endregion
private void OnBeginRequest(object sender, EventArgs e)
{
WriteTrace("Begin OnBeginRequest");
HttpContext ctx = HttpContext.Current;
try
{
string processor = ctx.Request.Params["p"];
if (processor != null && processor != "")
{
PluginProcessor(processor, ctx);
}
}
catch (Exception ex)
{
ctx.Response.StatusCode = 500;
ctx.Response.Write("ERROR");
}
ctx.ApplicationInstance.CompleteRequest();
WriteTrace("End OnBeginRequest");
}
private void PluginProcessor(string processor, HttpContext ctx)
{
string pluginSpec = AppConfig.GetAppSetting(processor.Trim().ToLower());
if (pluginSpec != "")
{
IWebhookProcessor proc = CreateProcessor(pluginSpec, ctx);
proc.Process(ctx);
}
}
private IWebhookProcessor CreateProcessor(string Processor, HttpContext ctx)
{
string assembly;
string typeName;
typeName = Processor.Substring(0, Processor.IndexOf(",")).Trim();
assembly = Path.Combine(ctx.Request.PhysicalApplicationPath, "bin", Processor.Substring(Processor.IndexOf(",") + 1).Trim());
var obj = Activator.CreateInstanceFrom(assembly, typeName);
return (Interfaces.IWebhookProcessor)obj.Unwrap();
}
}
So if the request doesn't map to a transaction handler, I'd like to 'hang' the client, but not in a way which will tie up resources on the server.
Thanks for your advice!
I think the best thing you can do is use HttpRequest.Abort(), which doesn't leave the client hanging, but it does immediately sever the TCP connection. Even the docs say it is for this kind of scenario:
You might use this method in response to an attack by a malicious HTTP client.
You would use it like this:
ctx.Request.Abort();
In a browser, you see a "connection reset" error.
Another option is to send back an unexpected HTTP status, like 400, or my personal favourite, 418.
Update: If you reaaallly want to make the client wait, you could implement your own HttpModule so that you can make an asynchronous BeginRequest event and then use Task.Delay().
The HttpModule class would look something like this:
public class AsyncHttpModule : IHttpModule {
public void Dispose() { }
public void Init(HttpApplication app) {
var wrapper = new EventHandlerTaskAsyncHelper(DoAsyncWork);
app.AddOnBeginRequestAsync(wrapper.BeginEventHandler, wrapper.EndEventHandler);
}
private async Task DoAsyncWork(object sender, EventArgs e) {
var app = (HttpApplication) sender;
var ctx = app.Context;
if (shouldDie) { //whatever your criteria is
await Task.Delay(60000); //wait for one minute
ctx.Request.Abort(); //kill the connection without replying
}
}
}
Then add the module in your web.config (replace the namespace with your app's namespace):
<system.webServer>
<modules>
<add name="AsyncHttpModule" type="MyNamespace.AsyncHttpModule" />
</modules>
</system.webServer>
Since this is asynchronous, it is not holding up a thread while it waits. Other requests that come in will use the same thread (I tested this).
However, it is still keeping the request context in memory, because the request is still in progress. So if they hit you with 1000+ requests, all of those 1000+ requests are held in memory for 60 seconds. Whereas if you just use HttpRequest.Abort() right away, those get removed from memory right away.

Create Windows Session programmatically from Console or Windows Service

How can I programmatically log in to windows to create a Windows Logon Session?
I need a way that works from a WinForms app, from a Console app, and (most important) from a Windows Service.
One other requirement is that I need it to work on a the local system that the program/service is running on and also for remote systems.
If there's a way to do this using pInvoke/Win32 API I am open to that too.
I found these similar questions/answers in my research:
Programmatically create and launch and RDP session (without gui)
The answer here says it's possible but and gives a link but the sample code from the link doesn't work
Create a Windows Session from a service via the Win32 API
No Solution to the question asked
Create Windows session programmatically
No Solution but the OP mentioned in a comment that http://freerdp.com worked for him.
I've created a simple utility that I believe meets all the requirements in the question. You'll need to add a COM reference to Microsoft Terminal Services Active Client 1.0 Type Library (ActiveX).
I thought it might not work for creating a session on the local machine but I tested in in 2012R2 running as a Service and it actually can. The same exact method can be called from a WinForms app or from a Console app. When launched from a WinForms or Console app, the a form is shown for a few seconds so I made sure to set the control to enabled = false so it can't be interacted with.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using AxMSTSCLib;
namespace Utility.RemoteDesktop
{
public class Client
{
private int LogonErrorCode { get; set; }
public void CreateRdpConnection(string server, string user, string domain, string password)
{
void ProcessTaskThread()
{
var form = new Form();
form.Load += (sender, args) =>
{
var rdpConnection = new AxMSTSCLib.AxMsRdpClient9NotSafeForScripting();
form.Controls.Add(rdpConnection);
rdpConnection.Server = server;
rdpConnection.Domain = domain;
rdpConnection.UserName = user;
rdpConnection.AdvancedSettings9.ClearTextPassword = password;
rdpConnection.AdvancedSettings9.EnableCredSspSupport = true;
if (true)
{
rdpConnection.OnDisconnected += RdpConnectionOnOnDisconnected;
rdpConnection.OnLoginComplete += RdpConnectionOnOnLoginComplete;
rdpConnection.OnLogonError += RdpConnectionOnOnLogonError;
}
rdpConnection.Connect();
rdpConnection.Enabled = false;
rdpConnection.Dock = DockStyle.Fill;
Application.Run(form);
};
form.Show();
}
var rdpClientThread = new Thread(ProcessTaskThread) { IsBackground = true };
rdpClientThread.SetApartmentState(ApartmentState.STA);
rdpClientThread.Start();
while (rdpClientThread.IsAlive)
{
Task.Delay(500).GetAwaiter().GetResult();
}
}
private void RdpConnectionOnOnLogonError(object sender, IMsTscAxEvents_OnLogonErrorEvent e)
{
LogonErrorCode = e.lError;
}
private void RdpConnectionOnOnLoginComplete(object sender, EventArgs e)
{
if (LogonErrorCode == -2)
{
Debug.WriteLine($" ## New Session Detected ##");
Task.Delay(10000).GetAwaiter().GetResult();
}
var rdpSession = (AxMsRdpClient9NotSafeForScripting)sender;
rdpSession.Disconnect();
}
private void RdpConnectionOnOnDisconnected(object sender, IMsTscAxEvents_OnDisconnectedEvent e)
{
Environment.Exit(0);
}
}
}
On a side note I found this question that says there may be a way to use the ActiveX control (for RDP) without using a windows form at all. I saw the example they gave and I was unsure hot to use their code for this situation.
ActiveX control without a form
If there's anyone out there who understands how to do this without hosting the ActiveX control on a Form please post an example.

How to programmatically stop an ASP.NET MVC application? [duplicate]

This question already has answers here:
How can I programmatically stop or start a website in IIS (6.0 and 7.0) using MsBuild?
(3 answers)
Closed 7 years ago.
I am using the MVC5 for an web application. The web app runs in IIS7 or greater.
In the Global.asax on application_start, the number of licenses will be set:
protected void Application_Start()
{
try
{
MyApp.cNumberOfLicenses = COM.GetNumberOfLicenses();
}
catch(Exception e)
{
// log exception
// stop web site.
}
}
If any expection will be thrown in this context, the web site should shut down as you can do that in the IIS-Manager:
How can I stop the current web site in my Application_Start ?
You can do it with the help of "Microsoft.Web.Administration.dll"
using Microsoft.Web.Administration;
After adding the reference of "Microsoft.Web.Administration.dll" write below code in Global.asax
protected void Application_Start(object sender, EventArgs e)
{
try
{
MyApp.cNumberOfLicenses = COM.GetNumberOfLicenses();
}
catch (Exception e)
{
// get the web site name
var lWebSiteName = System.Web.Hosting.HostingEnvironment.ApplicationHost.GetSiteName();
// log exception
// stop web site.
using (ServerManager smg = new ServerManager())
{
var site = smg.Sites.FirstOrDefault(s => s.Name == lWebSiteName);
if (site != null)
{
//stop the site...
site.Stop();
}
}
}
}
I will go not with stop it, but to show some message if you do not have license.
This is an example, and an idea.
You can use this code on global.asax where if you do not have licenses the moment you start, you open a flag, and after that you do not allow any page to show, and you send a page that you can keep on an html file.
private static bool fGotLicense = true;
protected void Application_Start()
{
try
{
MyApp.cNumberOfLicenses = COM.GetNumberOfLicenses();
}
catch(Exception e)
{
// log exception
// stop web site.
fGotLicense = false;
}
}
protected void Application_BeginRequest(Object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
// if not have license - let show some infos
if (!fGotLicens)
{
// the file we look now is the app_offline_alt.htm
string cOffLineFile = HttpRuntime.AppDomainAppPath + "app_offline_alt.htm";
// if exist on root
if (System.IO.File.Exists(cOffLineFile))
{
using (var fp = System.IO.File.OpenText(cOffLineFile))
{
// read it and send it to the browser
app.Response.Write(fp.ReadToEnd());
fp.Close();
}
}
// and stop the rest of processing
app.Response.End();
return;
}
}
You can have a file named app_offline.htm with content say This website is offline now in web server and copy that to root directoy of website you want for any event.
It will directly show that message, but yes, App pool will be still ON, when you need to start , you just need to rename that to something else.

Using Web API for a Windows Service to Receive Commands and Perform Tasks via Polling?

I have a project where I need to create a windows service that, when instructed via a command, will perform various tasks. This server would run on multiple servers and would effectively perform the same kind of tasks when requested.
For example, I would like to have a Web API service that listens for requests from the servers.
The service running on the server would send a query to Web API every 25 secs or so and pass to it its SERVERNAME. The Web API logic will then look up the SERVERNAME and look for any status updates for various tasks... I.E., if a status for a DELETE command is a 1, the service would delete the folder containing log files... if a status for a ZIP command is a 1, the service would zip the folder containing log files and FTP them to a centralized location.
This concept seems simple enough, and I think I need a nudge to tell me if this sounds like a good design. I'm thinking of using .NET 4.5 for the Windows Service, so that I can use the HttpClient object and, of course, .NET 4.5 for the Web API/MVC project.
Can someone please get me started on what a basic Web API woudld look like provide status updates to the Windows services that are running and issue commands to them...
I'm thinking of having a simple MVC website that folks will have a list of servers (maybe based on a simple XML file or something) that they can click various radio buttons to turn on "DELETE", "ZIP" or whatever, to trigger the task on the service.
I do something similar. I have a main Web API (a Windows Service) that drives my application and has a resource called /Heartbeat.
I also have a second Windows Service that has a timer fire every 30 seconds. Each time the timer fires it calls POST /heartbeat. When the heartbeat request is handled, it goes looking for tasks that have been scheduled.
The advantage of this approach is that the service makes the hearbeat request is extremely simple and never has to be updated. All the logic relating to what happens on a heartbeat is in the main service.
The guts of the service are this. It's old code so it is still using HttpWebRequest instead of HttpClient, but that's trivial to change.
public partial class HeartbeatService : ServiceBase {
readonly Timer _Timer = new System.Timers.Timer();
private string _heartbeatTarget;
public HeartbeatService() {
Trace.TraceInformation("Initializing Heartbeat Service");
InitializeComponent();
this.ServiceName = "TavisHeartbeat";
}
protected override void OnStart(string[] args) {
Trace.TraceInformation("Starting...");
_Timer.Enabled = true;
_Timer.Interval = Properties.Settings.Default.IntervalMinutes * 1000 * 60;
_Timer.Elapsed += new ElapsedEventHandler(_Timer_Elapsed);
_heartbeatTarget = Properties.Settings.Default.TargetUrl;
}
protected override void OnStop() {
_Timer.Enabled = false;
}
private void _Timer_Elapsed(object sender, ElapsedEventArgs e) {
Trace.TraceInformation("Heartbeat event triggered");
try {
var httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(_heartbeatTarget);
httpWebRequest.ContentLength = 0;
httpWebRequest.Method = "POST";
var response = (HttpWebResponse)httpWebRequest.GetResponse();
Trace.TraceInformation("Http Response : " + response.StatusCode + " " + response.StatusDescription);
} catch (Exception ex) {
string errorMessage = ex.Message;
while (ex.InnerException != null) {
errorMessage = errorMessage + Environment.NewLine + ex.InnerException.Message;
ex = ex.InnerException;
}
Trace.TraceError(errorMessage);
}
}
}
You can do it with ServiceController.ExecuteCommand() method from .NET.
With the method you can sand custom command to windows' service.
Then in your service you need to implement ServiceBase.OnCustomCommand() to serve incomming custom command event in service.
const int SmartRestart = 8;
...
//APPLICATION TO SEND COMMAND
service.ExecuteCommand(SmartRestart);
...
//SERVICE
protected override void OnCustomCommand(int command)
{
if (command == SmartRestart)
{
// ...
}
}

What is causing this DatabaseFileLockedException when trying to open a db4o database in an ASP.NET MVC app?

I'm building a small web application with ASP.NET MVC 2, using db4o as a datastore.
I have added an HttpModule—as per the example here—to give the application access to the db4o database, and everything is working perfectly on my development machine under the VS2008 ASP.NET Development Server.
However, when I deploy the app to my web host and try to access it, I get a DatabaseFileLockedException at the line where the HttpModule tries to open the database file. But there should be nothing else accessing the file; indeed on first run of the app it will only just have been created when this exception gets thrown.
The web host's servers are running IIS 7 on Windows Server 2008, and the application is running under Full Trust. It is a sub-application, in case that makes any difference.
I can't work out why this error is occurring on the live server, but not locally on my development server. Can anyone help me out or suggest what I should do next?
That's a mistake in the example-code. It assumes that the HttpModule.Init is only called once, which isn't necessarily true. Depending how your application is configured, it can be called multiple times. To fix this, check in the HttpModule-Handler if the instance is already there:
using System;
using System.Configuration;
using System.Web;
using Db4objects.Db4o;
namespace Db4oDoc.WebApp.Infrastructure
{
public class Db4oProvider : IHttpModule
{
private const string DataBaseInstance = "db4o-database-instance";
private const string SessionKey = "db4o-session";
// #example: open database when the application starts
public void Init(HttpApplication context)
{
if (null==HttpContext.Current.Application[DataBaseInstance])
{
HttpContext.Current.Application[DataBaseInstance] = OpenDatabase();
}
RegisterSessionCreation(context);
}
private IEmbeddedObjectContainer OpenDatabase()
{
string relativePath = ConfigurationSettings.AppSettings["DatabaseFileName"];
string filePath = HttpContext.Current.Server.MapPath(relativePath);
return Db4oEmbedded.OpenFile(filePath);
}
// #end example
// #example: close the database when the application shuts down
public void Dispose()
{
IDisposable toDispose = HttpContext.Current.Application[DataBaseInstance] as IDisposable;
if (null != toDispose)
{
toDispose.Dispose();
}
}
// #end example
// #example: provide access to the database
public static IObjectContainer Database
{
get { return (IObjectContainer)HttpContext.Current.Items[SessionKey]; }
}
// #end example
// #example: A object container per request
private void RegisterSessionCreation(HttpApplication httpApplication)
{
httpApplication.BeginRequest += OpenSession;
httpApplication.EndRequest += CloseSession;
}
private void OpenSession(object sender, EventArgs e)
{
IEmbeddedObjectContainer container =
(IEmbeddedObjectContainer)HttpContext.Current.Application[DataBaseInstance];
IObjectContainer session = container.OpenSession();
HttpContext.Current.Items[SessionKey] = session;
}
private void CloseSession(object sender, EventArgs e)
{
if (HttpContext.Current.Items[SessionKey] != null)
{
IObjectContainer session = (IObjectContainer)HttpContext.Current.Items[SessionKey];
session.Dispose();
}
}
// #end example
}
}
As alternative you could use the Application_Start from the Global.apsx, which is called only once for sure.
You have another problem here.
When AppPools restart there can be an overlap when the old AppPool is finishing request and the new AppPool is servicing new requests.
During this time you will have two processes trying to access the same db4o file
To get around this you can use something like the hack below.
Note the use of Db4oFactory.OpenServer instead of Db4oEmbedded.OpenFile. This allows the use of transactions on a more fine grained basis.
public IObjectServer OpenServer()
{
Logger.Debug("Waiting to open db4o server.");
var attempts = 0;
do
{
try
{
return Db4oFactory.OpenServer(fileName, 0);
}
catch (DatabaseFileLockedException ex)
{
attempts++;
if (attempts > 10)
{
throw new Exception("Couldn't open db4o server. Giving up!", ex);
}
Logger.Warn("Couldn't open db4o server. Trying again in 5sec.");
Thread.Sleep(5.Seconds());
}
} while (true);
}
Hope this helps
Sounds like permission issues if it works on dev. Stick a notepad file in the same directory and try to open that with some bare bones file code. I bet you'll have the same issue.

Categories