Привет всем.
Не так давно прочел статью Что нам стоит сайт распарсить. Основы webdriver API и вспомнил, что давно собирался довести хотя бы до относительно рабочего состояния одну забавную задумку. Руки все-таки дошли, а значит пора поведать, что же получилось.
Есть такая замечательная программа — Fiddler, позволяющая перехватывать и модифицировать http/https запросы. Есть замечательная штука под названием NW.js, она же node-webkit, позволяющая тыр… парсить разнообразные сайты в том числе. Вы красивы, я прекрасен — почему бы нам не подружиться?
Собственно, затея вот в чем: можно было бы, конечно, отдельно поднять Fiddler, написать в нем логику и гонять через него трафик с node-webkit-но это не так интересно. А значит, будем совмещать все под одной крышей, благо у Fiddler есть библиотека на C# — FiddlerCore.
Под ноду есть отличный модуль — Edge.js. Это такая хитрая штука, которая позволяет исполнять код C# (и не только). Есть под ноду? Замечательно, можно завести и под nw.js, благо даже мануал есть — да вот же он!


Итак, пропустим пару часов секаса по сборке этой замечательной библиотеки(сколько раз говорить себе, читай мануалы внимательно! Нужна 13 студия, не 12 и не 15!) и приступим к написанию кода. Останавливаться на подключении и загрузке модулей тоже не буду, предположим, что те, кого заинтересует эта статья, мануалы читать умеют(да понял я, понял, что мануалы нужно читать внимательно!).

//#r "System.Windows.Forms.dll"
//#r "fiddler/FiddlerCore.dll"

using Fiddler;
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

public class Startup
{
    Func<object, Task<object>> _console;
    Func<object, Task<object>> _html;

    public async void _print(object text){
        if(_console!=null)
            await _console(text);
    }
    
    public async void _getHtml(object html)
    {
        if (_html != null)
            await _html(html);
    }

    public async Task<object> Invoke(dynamic data)
    {
        _console = (Func<object, Task<object>>)data.console;
        _html = (Func<object, Task<object>>)data._html;

        _print("Started");
        FiddlerApplication.Shutdown();
        new FiddlerLogic
        {
            _beforeRequest = (oS) => {
                var proxy = oS.oRequest.headers["POverride"];
                if (proxy != null)
                {
                    oS["X-OverrideGateway"] = proxy;
                }
            },
            _beforeResponse = (oS) =>
            {
                var response = oS.GetResponseBodyAsString();
                _html(response);
            }
        }._start(5000);
        return Task.FromResult("Done");
    }
}


class FiddlerLogic
{
    public Action<Session> _beforeRequest;
    public Action<Session> _beforeResponse;

    public void _start(int port=5555)
    {
        FiddlerApplication.BeforeRequest += (oS) => _beforeRequest(oS);
        FiddlerApplication.BeforeResponse += (oS) => _beforeResponse(oS);

        FiddlerApplication.Startup(port, false, true);
    }
}


Итак, что же здесь происходит?
Эти вот 2 строки
//#r «System.Windows.Forms.dll»
//#r «fiddler/FiddlerCore.dll»

являются маркером для edge.js, нужны для подключения библиотек.
Класс FiddlerLogic — всего лишь небольшой враппер над Fiddler'ом, в котором был код для подключения сертификатов, но потом при помощи какой-то уличной магии(скорее всего, нашел в загашнике старую версию, которая этот самый код не требовала) оно заработало и так. Теперь этот класс, по факту, ничего особого не делает, но куда ж нынче без legacy-кода? Собственно, в конструкторе объекта мы указываем 2 callback'а, которые будут вызываться перед/после отправки запроса, порт(при необходимости) и все.
Ах да, в FiddlerApplication.Startup 2 и 3 аргументы отвечают за использование системного прокси/перехват https-запросов соответственно. Так как хотелось бы перехватывать https-запросы и нафиг не нужно использование системного прокси, значения — false и true.

Теперь про Startup. Это такой забавный класс, нужный для работы с edge.js(об этом модуле позже). Собственно, Invoke — это точка входа, console/_html — функции из js. FiddlerApplication.Shutdown() отвечает за завершение всех предыдущих инстансов фиддлера. В _beforeRequest происходит подмена прокси при наличии заголовка POverride в запросе. В _beforeResponse ничего шибко полезного не происходит, оставил для примера. Функции _print и _getHtml — просто обертки с проверкой на наличие переданных из js функций.

Теперь рассмотрим js-часть.

var edge = require('edge');
var gui = require('nw.gui'); 
var async = require('async');
var request = require('request');




var start=Date.now();
var arr=[];
var count=50;
var prev=Date.now();
var proxyList = ['160.92.56.41:80'];

_init();


for(var i=0; i<count; i++){
	arr.push('http://myip.ru/index_small.php')
}

var i=0;
var _node=function(url, c){
	console.log(url);
	var options = {
	  url: url,
	  headers: {
	    'POverride': proxyList[0]
	  }
	};
	request(options, function(err, response, html){
		if(err)
			console.log(err);
		var j=_html(html);
		console.log('Container:', j.find('.network-info tbody>:nth-child(2) td').text());
		c();
	});
};

async.map(arr, _node,
	function(){
		var ms=Date.now()-start;
		console.info('Node parse got %f seconds. Mid time: %f. Mid page per second: %f', ms/1000, ms/count, count*1000/ms);
	}
);









/***DEFINITIONS***/
function _html(html){ 
  return $('<div></div>').html(html);
};
function _init(){	
	try{
		request=request.defaults({'proxy':'http://localhost:5000'});
		gui.App.setProxyConfig("http://localhost:5000");
		func = edge.func("fiddler/Main.cs");
		func({
			console: function(data, callback){
				console.log(data);
			},
			_html: function(html, callback){
				//var container=_html(html);
				//console.log('Container:', container, container.find('.network-info tbody>:nth-child(2) td'));
			}
		});
	}
	catch(ex){
		console.log(ex);
	}
};


В первую очередь стоит обратить внимание на 2 функции снизу — _html и _init. Первая занимается непотребством в виде построения DOM из строки, 2 — базовые настройки и подключение C# кода.
Edge.func подгружает содержимое Main.cs(см. код выше), и передает в качестве аргументов ссылки на нужные функции. Собственно, аргумент функции func и есть data из public async Task

Комментарии (4)


  1. sopov
    17.12.2015 10:16

    Если есть DLL, то не проще ли использовать github.com/node-ffi/node-ffi


    1. Demogor
      17.12.2015 12:14

      DLL нет. код можно менять на лету в .CS. Кроме того. ffi умеет вроде только unmanaged dll. Впрочем. ffi я тоже использовал. но для других задач. удобная штука.


  1. RouR
    17.12.2015 22:29

    Собственно, затея вот в чем: можно было бы, конечно, отдельно поднять Fiddler, написать в нем логику и гонять через него трафик с node-webkit-но это не так интересно. А значит, будем совмещать все под одной крышей, благо у Fiddler есть библиотека на C#

    Что-то не уловил суть происходящего, зачем это делается, можете рассказать подробнее в контексте бизнес-логики?


    1. Demogor
      17.12.2015 23:11
      +1

      В самом посте содержится простой пример — возможность изменения прокси для каждого запроса по отдельности либо по необходимости. Кроме того, удобнее все держать в 1 пакете, нежели в отдельных приложениях, для каждого из которых нужно писать инструкцию в случае доставки конечному пользователю. Также fiddler позволяет перехватывать и менять не только управляемые пользователем запросы, а вообще весь входящий/исходящий трафик, включая(если не ошибаюсь) веб-сокеты. Соответственно, получаем достаточно удобный инструмент для отладки происходящего на сайте, включая те случаи, когда отладчика вебкита недостаточно(впрочем, никто не мешает использовать и его тоже). К примеру, та же консоль отлично подходит для тех случаев, когда нужно увидеть исходящие/входящие запросы, однако довольно проблематично поменять, к примеру, User-Agent.