Skip to content

Instantly share code, notes, and snippets.

@sedouard
Created November 24, 2013 06:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sedouard/7623912 to your computer and use it in GitHub Desktop.
Save sedouard/7623912 to your computer and use it in GitHub Desktop.
This sample (from the FiddlerCorApi sample) shows how you can create a "Mock" http service for your app. This allows you to "record" traffic between the target process and the web service. Then you can "replay" mode the "Mock" service will respond with the same exact responses the actual service did. The real awesome thing here is that you can t…
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Fiddler;
using System.IO;
using System.Diagnostics;
using Newtonsoft.Json;
using MockHttpServer.Data;
using HttpMockServer.Utils;
namespace MockHttpServer
{
class Program
{
static string[] processNames;
static string storagePath;
static string recordDelete;
static bool record;
static object mapLock = new object();
static Dictionary<int, HttpSnoop> mSnoops = new Dictionary<int, HttpSnoop>();
static void Main(string[] args)
{
ArgParser argParse = new ArgParser(args);
if(argParse.Count <= 0)
{
PrintUsage();
return;
}
processNames = argParse.AsString(0).Split(new string[]{";"}, StringSplitOptions.RemoveEmptyEntries);
storagePath = argParse.AsString(1);
recordDelete = argParse.AsString(2);
record = recordDelete.Equals("record", StringComparison.CurrentCultureIgnoreCase);
//
// It is important to understand that FiddlerCore calls event handlers on the
// session-handling thread. If you need to properly synchronize to the UI-thread
// (say, because you're adding the sessions to a list view) you must call .Invoke
// on a delegate on the window handle.
//
// Simply echo notifications to the console. Because Fiddler.CONFIG.QuietMode=true
// by default, we must handle notifying the user ourselves.
Fiddler.FiddlerApplication.OnNotification += delegate(object sender, NotificationEventArgs oNEA) { Console.WriteLine("** NotifyUser: " + oNEA.NotifyString); };
Fiddler.FiddlerApplication.Log.OnLogString += delegate(object sender, LogEventArgs oLEA) { Console.WriteLine("** LogString: " + oLEA.LogString); };
//load up the map file
if(!record)
{
using (FileStream fs = new FileStream(Path.Combine(storagePath, "map.json"), FileMode.Open, FileAccess.ReadWrite))
using (StreamReader sr = new StreamReader(fs))
{
mSnoops = JsonConvert.DeserializeObject<Dictionary<int, HttpSnoop>>(sr.ReadToEnd());
}
}
Fiddler.FiddlerApplication.BeforeRequest += delegate(Fiddler.Session oS)
{
Console.WriteLine("Before request for:\t" + oS.fullUrl);
// In order to enable response tampering, buffering mode must
// be enabled; this allows FiddlerCore to permit modification of
// the response in the BeforeResponse handler rather than streaming
// the response to the client as the response comes in.
oS.bBufferResponse = true;
bool isProcessOfInterest = false;
string processName = null;
foreach (Process p in Process.GetProcesses())
{
if (p.Id == oS.LocalProcessID)
{
var results = from x in processNames where Path.GetFileNameWithoutExtension(x).Equals(p.ProcessName, StringComparison.CurrentCultureIgnoreCase) select x;
int count = 0;
foreach(string s in results)
{
count++;
processName = s;
break;
}
if(count > 0)
{
isProcessOfInterest = true;
break;
}
}
}
if (isProcessOfInterest && !record && mSnoops.ContainsKey((KeyString(oS)).GetHashCode()))
{
oS.utilCreateResponseAndBypassServer();
//UpdateTimeStamp(Path.Combine(storagePath, processName) + (KeyString(oS)).GetHashCode().ToString() + ".txt");
oS.LoadResponseFromFile(Path.Combine(storagePath, processName) + (KeyString(oS)).GetHashCode().ToString() + ".txt");
Console.WriteLine("MOCKED RESPONSE FOR: " + KeyString(oS) + " with " + Path.Combine(storagePath, processName) + (KeyString(oS)).GetHashCode().ToString() + ".txt");
return;
}
int questionMark = oS.fullUrl.IndexOf("?");
if (!record && questionMark > 0 && isProcessOfInterest)
{
var result = from HttpSnoop x in mSnoops.Values where x.FileName.StartsWith(oS.RequestMethod+"."+oS.fullUrl.Substring(0, questionMark).Replace("/",".")) select x;
foreach (var snoop in result)
{
oS.utilCreateResponseAndBypassServer();
oS.LoadResponseFromFile(Path.Combine(storagePath, processName) + (snoop.FileName).GetHashCode().ToString() + ".txt");
Console.WriteLine("MOCKED !!!SIMILAR!!! RESPONSE FOR: " + KeyString(oS) + " with " + Path.Combine(storagePath, processName) + (snoop.FileName).GetHashCode().ToString() + ".txt");
oS.SaveResponse(Path.Combine(storagePath, processName) + (snoop.FileName).GetHashCode().ToString() + ".txt", false);
return;
}
}
if(isProcessOfInterest && !record)
{
Console.WriteLine("Error, no response for request: " + KeyString(oS));
oS.utilCreateResponseAndBypassServer();
return;
}
};
Fiddler.FiddlerApplication.BeforeResponse += delegate(Fiddler.Session oS)
{
bool isProcessOfInterest = false;
string processName = null;
foreach (Process p in Process.GetProcesses())
{
if (p.Id == oS.LocalProcessID)
{
var results = from x in processNames where Path.GetFileNameWithoutExtension(x).Equals(p.ProcessName, StringComparison.CurrentCultureIgnoreCase) select x;
int count = 0;
foreach (string s in results)
{
count++;
processName = s;
break;
}
if (count > 0)
{
isProcessOfInterest = true;
}
}
}
if (!record || !isProcessOfInterest)
{
return;
}
lock (mapLock)
{
if (!mSnoops.ContainsKey((KeyString(oS)).GetHashCode()))
{
try
{
oS.SaveResponse(Path.Combine(storagePath, processName) + (KeyString(oS)).GetHashCode().ToString() + ".txt", false);
Console.WriteLine("CACHED RESPONSE FOR: " + KeyString(oS));
}
catch (Exception e)
{
Console.WriteLine("ERROR: Could not save response - {0}", processName + (KeyString(oS)).GetHashCode().ToString() + ".txt -" + e.ToString());
}
mSnoops.Add((KeyString(oS)).GetHashCode(), new HttpSnoop()
{
FileName = KeyString(oS)
});
}
}
// Uncomment the following two statements to decompress/unchunk the
// HTTP response and subsequently modify any HTTP responses to replace
// instances of the word "Microsoft" with "Bayden"
//oS.utilDecodeResponse(); oS.utilReplaceInResponse("Microsoft", "Bayden");
};
FiddlerApplication.BeforeRequest += FiddlerApplication_BeforeRequest;
Fiddler.FiddlerApplication.AfterSessionComplete += delegate(Fiddler.Session oS) { Console.WriteLine("Finished session:\t" + oS.fullUrl); };
// Tell the system console to handle CTRL+C by calling our method that
// gracefully shuts down the FiddlerCore.
Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
Console.WriteLine("Starting FiddlerCore...");
// For the purposes of this demo, we'll forbid connections to HTTPS
// sites that use invalid certificates
Fiddler.CONFIG.IgnoreServerCertErrors = false;
// If you choose to decrypt HTTPS traffic, makecert.exe must
// be present in the Application folder.
Fiddler.FiddlerApplication.Startup(8877, true, false);
Console.WriteLine("Hit CTRL+C to end session.");
// Wait Forever for the user to hit CTRL+C.
// BUG BUG: Doesn't properly handle shutdown of Windows, etc.
Object forever = new Object();
lock (forever)
{
System.Threading.Monitor.Wait(forever);
}
}
private static void PrintUsage()
{
Console.WriteLine("Usage - ");
Console.WriteLine("Httpmockserver.exe <processname> <storageroot> <record/replay>");
}
static void FiddlerApplication_BeforeRequest(Session oSession)
{
}
/// <summary>
/// When the user hits CTRL+C, this event fires. We use this to shut down and unregister our FiddlerCore.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
Console.WriteLine("Shutting down...");
if (record)
{
Console.WriteLine("Saving response map file to " + Path.Combine(storagePath, "map.json"));
if (File.Exists(Path.Combine(storagePath, "map.json")))
{
File.Delete(Path.Combine(storagePath, "map.json"));
}
lock (mapLock)
{
using (FileStream s = new FileStream(Path.Combine(storagePath, "map.json"), FileMode.CreateNew, FileAccess.ReadWrite))
using (StreamWriter sw = new StreamWriter(s))
{
string snoopMap = JsonConvert.SerializeObject(mSnoops);
sw.WriteLine(snoopMap);
}
}
}
Fiddler.FiddlerApplication.Shutdown();
System.Threading.Thread.Sleep(750);
}
static string KeyString(Session session)
{
return session.RequestMethod + "." + session.fullUrl.Replace("/", ".");
}
//This is a really dumb hueristic that says the mock server has a compatible response if
//if the root url is the same before the where "?" character
static bool HasSimilarResponse(Session session)
{
int questionMark = session.fullUrl.IndexOf("?");
if (questionMark > 0)
{
var result = from HttpSnoop x in mSnoops.Values where x.FileName.StartsWith(session.RequestMethod + "." + session.fullUrl.Substring(0, questionMark).Replace("/", ".")) select x;
return result.Count() > 0;
}
else
{
return false;
}
}
}
//Simple object serialize http response file names.
class HttpSnoop
{
public string FileName { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment