Задать вопрос
zkrvndm
@zkrvndm
Архитектор решений

Как делать запросы через множество http-прокси одновременно?

Используя browser.webRequest.onBeforeSendHeaders я могу редактировать заголовки запросов. Можно ли при помощи него каким-нибудь образом вручную сконфигурировать полностью валидный запрос к http-прокси? Если вдруг да, то какая у запроса должна быть структура в плане отсылаемых заголовков и самого тела запроса?

Мне это нужно, чтобы иметь возможность одновременно делать запросы через разные прокси.
  • Вопрос задан
  • 626 просмотров
Подписаться 2 Сложный Комментировать
Решения вопроса 1
zkrvndm
@zkrvndm Автор вопроса
Архитектор решений
Поковырял мануалы и узнал, что оказывается Firefox позволяет работать с множеством прокси разом. Набросал код для совершения запросов через произвольные прокси хоть одновременно, хоть в разнобой.

Содержимое для вашего background.js:
Нажмите, чтобы раскрыть код
// Функция для совершения запросов:

function getText(link, timeout, options) {
	
	// Возвращаем промис для удобства:
	
	return new Promise(function(endPromise) {
		
		// Конвертируем опции запроса в строку:
		var bs64 = btoa(JSON.stringify(options));
		
		// Прячем опции в хеше:
		var uobj = new URL(link);
		uobj.hash = '#rp:'+bs64;
		var new_url = uobj.href;
		
		// Создаем новое соединение:
		var xhr = new XMLHttpRequest();
		
		// Открываем соединение:
		xhr.open('GET', new_url);
		
		// Указываем таймаут:
		xhr.timeout = timeout;
		
		// Функция для обработки результата:
		
		xhr.onreadystatechange = function() {
			
			// Если запрос был полностью завершен:
			
			if (xhr.readyState === XMLHttpRequest.DONE) {
				
				// Если запрос успешный:
				
				if (xhr.status === 200) {
					
					// Возвращаем текст ответа:
					endPromise(xhr.responseText);
					
				}
				
				// Если запрос не успешный:
				
				else {
					
					endPromise(false); // Возвращаем false
					
				}
				
			}
			
		}
		
		xhr.send(); // Отправляем запрос
		
	});
	
}

// Обработчик для проксирования некоторых запросов:

browser.proxy.onRequest.addListener(function(request) {
	
	// Считываем хеш из адреса запроса:
	var hash = new URL(request.url)['hash'];
	
	// Если хеш содержит
	// параметры запроса:
	
	if (/^#rp:/.test(hash)) {
		
		// Получаем из хеша параметры запроса и расшифровываем:
		var options = JSON.parse(atob(hash.replace(/^#rp:/, '')));
		
		// Если в параметрах запроса есть прокси:
		
		if (typeof options['proxy'] == 'object') {
			
			// Используем эту прокси:
			return options['proxy'];
			
		}
		
		// Если в опциях нет прокси:
		
		else {
			
			// Не используем прокси:
			return { 'type': 'direct' };
			
		}
		
	}
	
	// Если в хеше нет параметров:
	
	else {
		
		// Не используем прокси:
		return { 'type': 'direct' };
		
	}
	
}, { urls: [ '<all_urls>' ] });

// Обработчик для автоматической авторизация на http-прокси:

browser.webRequest.onAuthRequired.addListener(function(request) {
	
	// Считываем хеш из адреса запроса:
	var hash = new URL(request.url)['hash'];
	
	// Если хеш содержит
	// параметры запроса:
	
	if (/^#rp:/.test(hash)) {
		
		// Получаем из хеша параметры запроса и расшифровываем:
		var options = JSON.parse(atob(hash.replace(/^#rp:/, '')));
		
		// Если в параметрах запроса есть прокси:
		
		if (typeof options['proxy'] == 'object') {
			
			// Если в параметрах тип прокси http:
			
			if (options['proxy']['type'] == 'http') {
				
				// Отдаем логин с паролем для авторизации:
				
				return {
					'authCredentials': {
						'username': options['proxy']['username'],
						'password': options['proxy']['password']
					} 
				};
				
			}
			
		}
		
	}
	
}, { urls: [ '<all_urls>' ] }, [ 'blocking' ]);

// Обработчик для редактирования исходящих заголовков в запросах:

browser.webRequest.onBeforeSendHeaders.addListener(function(request) {
	
	// Считываем хеш из адреса запроса:
	var hash = new URL(request.url)['hash'];
	
	// Если хеш содержит
	// параметры запроса:
	
	if (/^#rp:/.test(hash)) {
		
		// Получаем из хеша параметры запроса и расшифровываем:
		var options = JSON.parse(atob(hash.replace(/^#rp:/, '')));
		
		// Берём массив заголовков запроса:
		var headers = request.requestHeaders;
		
		// Перебираем заголовки в обратном порядке:
		
		for (var n = headers.length - 1; n >= 0; n--) {
			
			// Вытаскиваем название текущего заголовка:
			var header = headers[n].name.toLowerCase();
			
			// Если текущий заголовок это Origin, Referer или Cookie:
			
			if (header == 'origin' || header == 'referer' || header == 'cookie') {
				
				// Удаляем заголовок:
				headers.splice(n, 1);
				
			}
			
		}
		
		// Если передан Origin:
		
		if (options['origin']) {
			
			// Добавляем заголовок Origin в массив заголовков запроса:
			headers.push({ name: 'Origin', value: options['origin'] });
			
		}
		
		// Если передан Referer:
		
		if (options['referer']) {
			
			// Добавляем заголовок Referer в массив заголовков запроса:
			headers.push({ name: 'Referer', value: options['referer'] });
			
		}
		
		// Если передан Cookie:
		
		if (options['cookie']) {
			
			// Добавляем заголовок Cookie в массив заголовков запроса:
			headers.push({ name: 'Cookie', value: options['cookie'] });
			
		}
		
		// Возвращаем изменённый
		// массив заголовок запроса:
		return {requestHeaders: headers};
		
	}
	
}, {urls: [ '<all_urls>' ] }, [ 'blocking', 'requestHeaders' ]);

// Обработчик для редактирования входящих заголовков:

browser.webRequest.onHeadersReceived.addListener(function(request) {
	
	// Считываем хеш из адреса запроса:
	var hash = new URL(request.url)['hash'];
	
	// Если хеш содержит
	// параметры запроса:
	
	if (/^#rp:/.test(hash)) {
		
		// Берём массив заголовков запроса:
		var headers = request.responseHeaders;
		
		// Перебираем заголовки в обратном порядке:
		
		for (var n = headers.length - 1; n >= 0; n--) {
			
			// Вытаскиваем название текущего заголовка:
			var header = headers[n].name.toLowerCase();
			
			// Если это установка куков:
			
			if (header == 'set-cookie') {
				
				// Удаляем заголовок:
				headers.splice(n, 1);
				
			}
			
		}
		
		// Добавляем заголовок, что ответ должен быть доступен всем:
		headers.push({ name: 'Access-Control-Allow-Origin', value: '*' });
		
		// Возвращаем изменённый
		// массив заголовок запроса:
		return {responseHeaders: headers};
		
	}
	
}, { urls: [ '<all_urls>' ] }, [ 'blocking', 'responseHeaders' ]);

Пример совершения запроса через socks5-прокси:
await getText('https://yandex.ru', 60000, {
	'proxy': {
		'type': 'socks',
		'host': '185.38.84.111',
		'port': '65234',
		'username': 'тут_логин',
		'password': 'тут_пароль'
	},
	'origin': 'https://yandex.ru',
	'referer': 'https://yandex.ru/test',
	'cookie': 'test1=123; iii=ooo'
});

Пример совершения запроса через http-прокси:
await getText('https://yandex.ru', 60000, {
	'proxy': {
		'type': 'http',
		'host': '185.38.84.111',
		'port': '65233',
		'username': 'тут_логин',
		'password': 'тут_пароль'
	},
	'origin': 'https://yandex.ru',
	'referer': 'https://yandex.ru/test',
	'cookie': 'test1=123; iii=ooo'
});

Вы можете также передавать произвольные Origin, Referer и Cookie, чтобы получше замаскироваться.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
У HTTP прокси несколько другой формат запроса, в обычном HTTP запросе

GET /somepath HTTP/1.1
Host: example.com

в случае прокси запрос должен быть

GET http://example.com/somepath HTTP/1.1
Host: example.com


поэтому либо прокси должен поддерживать запросы в стиле веб-сервера (т.н. транспарентный прокси) либо надо иметь возможно менять не только заголовки, но и сам запрос. В некоторых прокси транспарентные запросы принимаются "из коробки", в других (например squid) их надо разрешать соответствующе директивой, где-то они могут совсем не поддерживаться. Будет ли работать транспарентный режим в сочетании с Proxy-Authorization - так же зависит от прокси.

В случае HTTPS манипуляция заголовками не даст возможности работать с прокси, надо иметь возможность делать отдельный запрос (CONNECT) перед установкой TLS-соединения

Вы можете решить вашу проблему установив локальный транспарентный прокси без аутентификации, который будет перенаправлять трафик в родительский прокси с аутентификацией, например для 3proxy:
auth iponly
allow *
parent 1000 http адрес порт логин пароль
proxy -i127.0.0.1 -p3128
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы