This web page requires JavaScript to be enabled.

JavaScript is an object-oriented computer programming language commonly used to create interactive effects within web browsers.

How to enable JavaScript?

Fiddler – Một web debugging proxy và tùy biến FiddlerScript

Blog December 26, 2020 Nam Le 0

Giới thiệu sơ về Fiddler

NREADY.NET, Nam Le – Fiddler là một web debugging proxy mà chắc chắn ai trong ngành CNTT đều đã từng xài qua hoặc ít nhất là cũng biết đến bên cạnh Wireshark.

Fiddler được sử dụng để ghi nhật ký, kiểm tra và thay đổi lưu lượng HTTP và HTTPS giữa máy tính và máy chủ web hoặc các máy chủ,… và có khá nhiều tiện ích hay ho, bên cạnh những tính năng đã kể ra thì ta có thể tùy biến các Script của Fiddler để thực hiện các tác vụ chuyên biệt hơn mà có thể rất cần thiết trong phát triển các API, kiểm thử API hay cơ bản là "vọc" các app có giao tiếp Client – Server.

Hiện tại Fiddler được nâng cấp lên bản Fiddler Everywhere khá lạ lẫm nhưng có thể chạy được trên đa nền tảng bao gồm Linux, macOS và cả Windows. Có thể dùng thay thế Postman trong phát triển API.

Trong bài viết này sẽ giới thiệu vài tiện ích mà ta có thể thực hiện với Fiddler thông qua việc tùy biến (Custom) FiddlerScript, và mình nghĩ nên dùng bản Fiddler Classic.

FiddlerScript là gì?

FiddlerScript hiểu cơ bản nó chính là các tập lệnh dựa trên JScript.NET, một phiên bản .NET của JavaScript. Vì thế nên các cấu trúc lệnh của FiddlerScript có sự giao thoa giữa C# và JavaScript. Đường dẫn mặc định tại của FiddlerScript tại C:\Users\xxx\Documents\Fiddler2\Scripts\CustomRules.js

Đây là link github của mình về FiddlerScript https://github.com/leqnam/Custom-FiddlerScript, để sử dụng CustomRules.js, trong cửa sổ chính của Fiddler, chọn menu Rule -> Customize Rule… hoặc tổ hợp phím {Ctrl} + {R}. Mình sẽ trích ra vài tiện ích để giới thiệu:

RequestResponseTime

public static RulesOption("&Show RequestResponseTime", "NREADY Labs")
var m_requestTimeOn: boolean = true;

public static BindUIColumn("RequestResponseTime", 180)

function RequestResponseTime(oS: Session) {
    if (oS.Timers != null) {
        return oS.Timers.ClientBeginRequest.ToString("HH:mm:ss.ffff") + " -> " + oS.Timers.ClientDoneResponse.ToString("HH:mm:ss.ffff");
    }
    return String.Empty;
}

Crawl Sequential URLs

public static ToolsAction("Crawl Sequential URLs", "NREADY Lab")

function doCrawl() {
    var sBase: String;
    var sInt: String;

    sBase = FiddlerObject.prompt("Enter base URL with ## in place of the start integer", "http://");
    sInt = FiddlerObject.prompt("Start At", "1");
    var iFirst = int.Parse(sInt);
    sInt = FiddlerObject.prompt("End At", "100");
    var iLast = int.Parse(sInt);

    for (var x = iFirst; x <= iLast; x++) {
        //Replace 's' with your HTTP Request. Note: \ is a special character in JScript
        // If you want to represent a backslash in a string constant, double it like \\ 
        var s = "GET " + sBase.Replace("##", x.ToString()) + " HTTP/1.0\r\n\r\n";
        var b = false;
        while (!b) {
            try {
                FiddlerObject.utilIssueRequest(s);
                b = true;
            } catch (e) {
                var iT = Environment.TickCount + 10000;
                FiddlerObject.StatusText = "Waiting 10 sec because we have too many requests outstanding...";
                while (iT > Environment.TickCount) { Application.DoEvents(); }
            }
        }
    }
}

Replay Sequential URLs

public static ToolsAction("Replay Sequential URLs", "NREADY Lab")

function doReplay() {
    var sBase: String;
    var sInt: String;

    sBase = FiddlerObject.prompt("Enter base URL", "http://");
    sInt = FiddlerObject.prompt("Times=", "1");
    var n = int.Parse(sInt);

    for (var i = 1; i <= n; i++) {
        var s = "GET " + sBase + " HTTP/1.1\r\n\r\n";
        var b = false;
        while (!b) {
            try {
                FiddlerObject.utilIssueRequest(s);
                b = true;
            } catch (e) {
                var iT = Environment.TickCount + 10000;
                FiddlerObject.StatusText = "Waiting 10 sec because we have too many requests outstanding...";
                while (iT > Environment.TickCount) { Application.DoEvents(); }
            }
        }
    }
}

Filter Processes and Domain

Filter traffic with specific process and specific domain request. Declare the Rule option before OnBeforeRequest() function:

public static RulesOption("&Filter Processes and Domain", "NREADY Labs")
var m_filterOn: boolean = true;

Inside OnBeforeRequest():

if (m_filterOn) {
    oSession["ui-hide"] = "true";
    // Some common processes:  IE - iexplore.exe; chrome - chrome.exe, MS Edge = msedge, IIS Express - iisexpress.exe, Powershell - powershell.exe
    // List of processes to filter on
    var processlist = ["chrome", "msedge", "iisexpress", "powershell"];
    // List of domain names to filter on
    var host = ["localhost", "nready.net"];
    for (var j = 0; j < processlist.length; j++) {
        if (oSession.LocalProcess.Contains(processlist[j])) {
            for (var i = 0; i < host.length; i++) {
                if (oSession.HostnameIs(host[i])) {
                    oSession["ui-hide"] = null;
                }
            }
        }
    }
}

Show Time-to-Last-Byte

Show the duration between the start of Request.Send and Response.Completed in Milliseconds with color in red, yellow or orange.

public static RulesOption("&Show Time-to-Last-Byte", "NREADY Labs")
var m_ShowTTLB: boolean = true;

Inside OnBeforeResponse()

if (m_ShowTTLB) {
    oSession["ui-customcolumn"] = oSession.oResponse.iTTFB + "/" + oSession.oResponse.iTTLB + "ms " + oSession["ui-customcolumn"];

    if (oSession.oResponse.iTTLB >= 1000) { oSession["ui-backcolor"] = "yellow"; }
    if (oSession.oResponse.iTTLB >= 3000) { oSession["ui-backcolor"] = "orange"; }
    if (oSession.oResponse.iTTLB >= 10000) { oSession["ui-backcolor"] = "red"; }
}

CopyTimers

public static ContextAction("CopyTimers")

function CopyTimers(oSessions: Fiddler.Session[]) {
    if (null == oSessions) {
        MessageBox.Show("Please select sessions to copy timers for.", "Nothing to Do");
        return;
    }
    var s: System.Text.StringBuilder = new System.Text.StringBuilder();
    for (var x = 0; x < oSessions.Length; x++) {
        s.AppendFormat("ClientConnected {0}\nClientDoneRequest {1}\nServerConnected {2}\nServerGotRequest {3}\nServerBeginResponse {4}\nServerDoneResponse {5}\nClientBeginResponse {6}\nClientDoneResponse {7}\r\n",
            oSessions[x].Timers.ClientConnected,
            oSessions[x].Timers.ClientDoneRequest,
            oSessions[x].Timers.ServerConnected,
            oSessions[x].Timers.ServerGotRequest,
            oSessions[x].Timers.ServerBeginResponse,
            oSessions[x].Timers.ServerDoneResponse,
            oSessions[x].Timers.ClientBeginResponse,
            oSessions[x].Timers.ClientDoneResponse
        );
    }
    Utilities.CopyToClipboard(s.ToString());
    // MessageBox.Show("Done.");
    FiddlerObject.StatusText = "Coppied ...";
}

Call Sequence DELETE for Rest API

Use for Sequence call Detele for Restful API with header got from Session[]. The Session[] is from ContextMenu action on any request that we choice

public static ContextAction("Sequence DELETE")

function Seq_DELETE(arrSess: Session[]) {
    var oRH = '';
    var headerBuilder: System.Text.StringBuilder = new System.Text.StringBuilder();
    var sBase: String;
    var sInt: String;
    var sAuth: String;
    var sCookie: String;
    var baseURL = '';
    var baseAuthorization = '';
    var baseCooki = '';
    for (var i: int = 0; i < arrSess.Length; i++) {
        baseURL = arrSess[i].fullUrl;
        baseAuthorization = arrSess[i].oRequest["Authorization"];
        baseCooki = arrSess[i].oRequest["Cookie"];
    }
    sBase = FiddlerObject.prompt("Base URL with ## in place of the start integer", baseURL);
    sInt = FiddlerObject.prompt("Start At", "1");
    var iFirst = int.Parse(sInt);
    sInt = FiddlerObject.prompt("End At", "100");
    var iLast = int.Parse(sInt);
    sAuth = FiddlerObject.prompt("Authorization", baseAuthorization);
    sCookie = FiddlerObject.prompt("Cookie", baseCooki);
    for (var x = iFirst; x <= iLast; x++) {
        headerBuilder.AppendFormat("DELETE {0} HTTP/1.1\r\nAuthorization: {1}\r\nCookie: {2}\r\n\r\n",
            sBase.Replace("##", x.ToString()),
            sAuth,
            sCookie);
        var b = false;
        while (!b) {
            try {
                FiddlerObject.utilIssueRequest(headerBuilder);
                b = true;
            } catch (e) {
                //  var iT = Environment.TickCount + 2000;
                MessageBox.Show(e.ToString());
                //FiddlerObject.StatusText = e.ToString();// "Waiting 2 sec because we have too many requests outstanding...";
                //  while (iT > Environment.TickCount) { Application.DoEvents(); }
            }
        }
    }
    MessageBox.Show("Done ...");
    FiddlerObject.StatusText = "Done ...";
}

Clean Header Request & Response

public static ContextAction("Clean Header Request & Response")

function doClean(arrSess: Session[]) {
    for (var i: int = 0; i < arrSess.Length; i++) {
        arrSess[i].utilDecodeResponse();
        var oRH = arrSess[i].oRequest.headers;
        oRH.RemoveRange(["Cookie",
            "Authorization",
            "User-Agent",
            "Accept"
        ]);
        oRH = arrSess[i].oResponse.headers;
        oRH.RemoveRange(["X-Powered-By",
            "Last-Modified",
            "Accept-Ranges",
            "Connection",
            "Server",
            "Cache-Control",
            "Date",
            "X-Cache",
            "Expires",
            "Age",
            "Access-Control-Allow-Origin",
            "X-Frame-Options",
            "X-XSS-Protection",
            "Keep-Alive"
        ]);
    }
    MessageBox.Show("Done");
}

Documentations

Credits


Nam Le
lequocnam



0 responds

Leave a Reply

Your email address will not be published. Required fields are marked *