热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

HTTPProxy在自动化测试中的运用及精简实现

HTTPProxy是一个中间程序,它既可以担当浏览器(客户端)的角色也可以担当WebServer(服务器)的角色。HTTPProxy代表浏览器向WebServer发送请求,浏览

       HTTPProxy是一个中间程序,它既可以担当浏览器(客户端)的角色也可以担当WebServer(服务器)的角色。HTTPProxy代表浏览器向WebServer发送请求,浏览器的请求经过代理,会在代理内部得到服务或者经过一定的转换转至WebServer。一个代理必须能同时实现HTTP/1.1协议规范(RFC2616)中对客户端和服务器所作的要求。

       HTTPProxy在自动化测试中所占的地位十分重要,并作为非透明代理得到广泛应用,非透明代理(non-transparent proxy)需修改请求或响应,以便为用户代理提供附加服务,这些附加服务主要包括:

(1)对于某些性能测试工具如LoadRunner、JMeter等,通过HTTPProxy实现对HTTP请求和响应进行详细日志记录,并通过脚本分析生成器对日志脚本化,从而实现对测试脚本的录制功能;

(2)对于某些安全性测试工具如SPIKE Proxy,通过HTTPProxy在实现用户访问行为的同时,对这些行为自动应用变异技术,构造Fuzzing数据执行模糊测试。

       HTTPProxy非透明代理实现的关键在于:

(1)首先,它要成为一个功能完整的HTTPProxy,就必须依照RFC2616中对于代理的要求加以实现,如必须处理响应头属性中包含Transfer-Encoding:chunked的情况;

(2)加入附加服务,如提供对于脚本的实时录制转换或记录日志之后再分析进行脚本化的服务,都必须依赖对于HTTP请求和响应的详细记录,这不仅仅是单纯的对浏览器与WebServer之间HTTP协议数据往来的僵化记录,而是还需要处理如响应头属性中包含如Content-Encoding:gzip的各类情况;又如提供对于访问行为自动应用变异技术的服务,就需要代理内部实现一个完善的模糊器;

(3)实现一个小型的Web容器,容器对所有接入代理的浏览器提供服务,成为管理控制入口。

       一个Java版本的HTTPProxy的精简实现如下:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.Runnable;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.UnresolvedAddressException;

public class SimpleProxy implements Runnable{
	
	private final static int BYTEBUFFER_CAPACITY = 1024 * 1024 * 2;//设定字节缓冲区的内存空间大小,用于保存HTTP各类数据
	
	private final static int DEFAULT_HTTP_PORT = 80;//默认的HTTP协议服务端口
	private final static int DEFAULT_HTTPS_PORT = 443;//默认的HTTPS协议服务端口

	private SocketChannel inChannel = null;//代理与浏览器通信Socket
	private SocketChannel outChannel = null;//代理与Web服务器通信Socket
	
	private Socket socket = null;//用于处理代理与Web服务器间通过HTTPS通信 
	
	private InputStream in = null;//代理从Web服务器读取数据的InputStream对象
	
	private ByteBuffer buffer = ByteBuffer.allocateDirect(BYTEBUFFER_CAPACITY);//分配一个字节缓冲区用于处理inChannel通道和outChannel通道的数据
	
	//构造函数
	public SimpleProxy(SocketChannel inChannel){
		
		this.inChannel = inChannel;
	}
	
	public void run(){
		proxy();
	}
	
	private void proxy(){

		String httpRequestHandlerData = fromBrowserToProxy();//处理从浏览器到代理的逻辑
		
		//如果从浏览器到代理请求完成,会初始化outChannel和in对象用于处理代理与Web服务器之间的通信
		if(in != null){//初始化成功
			
			fromProxyToWebServer(httpRequestHandlerData);//处理代理到Web服务器的逻辑
			
			fromWebServerToProxy();//处理Web服务器到代理的逻辑
				
			fromProxyToBrowser();//处理代理到浏览器的逻辑
		}
		
	
		//执行完该代理连接逻辑后,设置字节缓冲区为空
		buffer = null;
		
		//关闭一系列对象
		if(in != null){
			try {
				in.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(socket != null){
			try {
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		if(outChannel != null){
			try {
				outChannel.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		if(inChannel != null){
			try {
				inChannel.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		//由于使用字节缓冲区ByteBuffer,因此强制执行GC操作,回收非虚拟机堆栈空间的内存
		System.gc();
	}
	
	/**
	 * 处理从浏览器到代理的逻辑
	 * @return 返回一个被处理后的HTTP Request Data
	 */
	private String fromBrowserToProxy(){
		
		String httpRequestRawData = receiveHttpRequest();
		String[] httpRequestInfor = httpRequestHandler(httpRequestRawData);//对从浏览器发起的HTTP Request Data进行处理,获取和处理相关信息
		String hostValue = httpRequestInfor[0];//获取Host值
		boolean isSSL = Boolean.getBoolean(httpRequestInfor[1]);//判断是否为HTTPS
		String httpRequestHandledData = httpRequestInfor[2];//获取处理后的HTTP Request Data
		String host;//用于代理与Web服务器建立Socket连接的host
		int port;//用于代理与Web服务器建立Socket连接的port
		
		if(hostValue.contains(":")) {//如果Host值包含':',说明使用了非默认端口
			host = hostValue.split(":")[0].trim();
			port = Integer.valueOf(hostValue.split(":")[1].trim());
		} else {//使用了默认端口
			host = hostValue;
			if(!isSSL){//HTTP协议默认端口
				port = DEFAULT_HTTP_PORT;
			} else {//HTTPS协议默认端口
				port = DEFAULT_HTTPS_PORT;
			}
		}
		if(host.length() > 0){//获取了host信息
			
			//初始化代理与Web服务器的Socket连接
			createOutChannel(host, port, isSSL);
		}
		
		return httpRequestHandledData;
		
	}
	
	private void fromProxyToWebServer(String httpRequestHandlerData){
		PrintWriter out = null;//用于发送HTTPS数据
		
		buffer.clear();//清空buffer准备写入
		
		//如果HTTP Request Header中带有Referer,进行处理
		buffer.put(httpRequestHandlerData.getBytes());
			
		buffer.flip();//翻转buffer准备读出
		
		if(outChannel != null){
	
			while(buffer.hasRemaining()){
				try {
					outChannel.write(buffer);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					buffer.clear();
				}//向Web服务器发送HTTP Request
			}
		} else if(socket != null) {
			try {
				out = new PrintWriter(socket.getOutputStream());
				out.write(httpRequestHandlerData);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			finally{
				out.close();
			}
		}
	}
	
	private void fromWebServerToProxy(){
		
		StringBuffer stringBuffer = new StringBuffer();
	
		buffer.clear();
		
		HTTPCommonMethod hcm = new HTTPCommonMethod();
		
		//获取Web服务器HTTP Response Header信息
		int headerLength = hcm.readHTTPResponseHeader(buffer,in);
		
		for(int i = 0;i  
 
public class HTTPResponseHeaderHandler {
	private final String CRLF = "\r\n";
	private final String CR = "\r";
	
	private String httpCOntent= null;
	CommonMethod cm = new CommonMethod();
	
	public HTTPResponseHeaderHandler(String httpContent){
		this.httpCOntent= httpContent;
	}
	
	/*********************************
	 * 处理带有chunked的HTTP Response Header
	 * @param contentLengthValue
	 * @return
	 ************************************/
	public String chunkedHandle(int contentLengthValue){
		StringBuffer sb = new StringBuffer();
		String[] headerLines = httpContent.split(CRLF);
		for(int i = 0; i  
 
public class HTTPRequestHeaderHandler {
	
	private final String CRLF = "\r\n";
	private final String CR = "\r";
	
	
	private String httpCOntent= null;
	CommonMethod cm = new CommonMethod();
	
	public HTTPRequestHeaderHandler(String httpContent){
		this.httpCOntent= httpContent;
	}
	
	/************************
	 * 获取HTTP Request中的Host内容
	 * @return
	 */
	public String getHostValue(){
		
		if(httpContent.contains("Host:")){
			String value = cm.string_param_save(httpContent, "Host:", CR,0);
			return value;
		} else if(httpContent.split(CRLF)[0].contains("http://")) {
			String value = cm.string_param_save(httpContent.split(CRLF)[0], "http://", "/",0);
			return value;
		} else if(httpContent.split(CRLF)[0].contains("https://")) {
			String value = cm.string_param_save(httpContent.split(CRLF)[0], "https://", "/",0);
			return value;
		} else {
			
			return "";
		}
	}
	
	public boolean isSSL(){
		if(httpContent.split("CRLF")[0].contains("https://")){
			return true;
		}
		else {
			return false;
		}
	}
	/************************
	 * 获取HTTP Request中的HTTP版本内容
	 * @return
	 */
	public String getHTTPVersion(){
		if(httpContent.contains("HTTP/")){
			String value = cm.string_param_save(httpContent, "HTTP/", " ",0);
			return value;
		}
		else {
			return "";
		}
	}
	
	
	public String handle(String hostValue){
		
		if(httpContent.contains("http://")){
			return httpContent.replace("http://" + hostValue, "");
		}
		else if(httpContent.contains("https://")){
			return httpContent.replace("https://" + hostValue, "");
		}

		return httpContent;
	}
}
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CommonMethod {


public String string_param_save(String httpContent, String leftedge, String rightedge, int index){
		
		if(httpContent.toLowerCase().contains(leftedge.toLowerCase()))
		{
			ArrayList paramList = new ArrayList(2);
			Pattern pa = Pattern.compile(Pattern.quote(leftedge) + "(.*?)" + Pattern.quote(rightedge),Pattern.CASE_INSENSITIVE);
			Matcher ma = pa.matcher(httpContent);
			while(ma.find()){
				
				paramList.add(ma.group(1));
			}
			return paramList.get(index);
		} else {
			return null;
		}
	}
	
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.zip.GZIPInputStream;

public class HTTPCommonMethod {
	private final int GZIP_DECOMPRESS_BUFFER_SIZE = 10240;
	private final char CR = '\r';
	
	/********************
	 * 用于处理HTTP Response Header中具有"Content-Encoding:gzip"的HTTP Response实体数据的解压缩
	 * @param in gzip压缩数据的InputStream
	 * @param out gzip解压数据的OutputStream
	 * @throws IOException 如果输入不符合gzip压缩格式抛出异常
	 */
	public void decompress(InputStream in, OutputStream out) throws IOException{
		GZIPInputStream gis = new GZIPInputStream(in);
		byte[] buffer = new byte[GZIP_DECOMPRESS_BUFFER_SIZE];
		int count;
		//循环处理知道解压完成
		while((count = gis.read(buffer, 0, GZIP_DECOMPRESS_BUFFER_SIZE)) != -1){
			out.write(buffer,0,count);
		}
		gis.close();
	}
	
	
	/***********************
	 * 主要用于处理对HTTP Response Header数据的读取,也用于某些特殊的不含有Header信息的HTTP Response实体数据的读取
	 * @param buffer
	 * @param in
	 * @return
	 */
	public int readHTTPResponseHeader(ByteBuffer buffer,InputStream in){
		int httpRespOnseHeaderSize= 0;//记录HTTP Response Header的大小
		buffer.clear();//清空ByteBuffer准备写入
		
		/***************************
		 * 循环读取,直到遇到一个空行的CRLF
		 ***************************/
		while(true){
			
			try {
					char ch = (char)in.read();
					//某些不含有Header信息的HTTP Response数据会以字节65535为结束符
					if((int)ch == 65535){
						break;
					}
					
					buffer.put((byte)ch);
					httpResponseHeaderSize++;
					//直到两个连续的CRLF循环终止
					if(ch == CR){
						buffer.put(((byte)in.read()));//读取\n
						httpResponseHeaderSize++;
						ch = (char)in.read();//读取\r
						httpResponseHeaderSize++;
						buffer.put((byte)ch);
						if(ch == CR){
							buffer.put(((byte)in.read()));//读取\n
							httpResponseHeaderSize++;
							
							buffer.flip();
							break;
						}	
				} 
				
			} catch (IOException e) {
				e.printStackTrace();
				break;
			}
		}
		return httpResponseHeaderSize;
	}
	

	/*********************
	 * 用于处理HTTP Response Header中具有"Transfer-Encoding:chunked"的HTTP Response实体数据的读取
	 * @param chunkEntityBuffer 记录chunkEntity内容的ByteBuffer
	 * @param in 与Web服务器建立连接的输入流
	 * @return 返回Content-Length值
	 */
	public int chunkResponseHandler(ByteBuffer chunkEntityBuffer,InputStream in){
		
		chunkEntityBuffer.clear();//清空ByteBuffer准备写入
		int cOntentLength= 0;//Content-Length值最后经过累加,遵循HTTP协议的要求,对chunk内容组合,向浏览器返回一个具有Content-Length值的HTTP Response
		StringBuffer chunkSizeBuffer = null;
		String hexChunkSize = "";
		while(true){
			
			chunkSizeBuffer = new StringBuffer();
			
			/*********************
			 * 读取Chunk-Size并记录
			 **********************/
			char chunkSizeChar;
			
			try {
					while((chunkSizeChar = (char)in.read()) != CR){
						chunkSizeBuffer.append(chunkSizeChar);
					}
					in.read();//读取\n,实现读取CRLF
			} catch (IOException e) {
				
				e.printStackTrace();
				break;
			}
			
			hexChunkSize = chunkSizeBuffer.toString();//得到一个Hex数字值的Chunk-Size
			
			//如果Chunk-Size为0,表示到达Chunk块的末尾,将ByteBuffer翻转以便读取,退出循环
			if(Integer.decode("0x" + hexChunkSize) == 0){
				chunkEntityBuffer.flip();
				break;
			}
			
			contentLength += Integer.decode("0x" + hexChunkSize);//累加取得Content-Length大小
			
			/******************************
			 * 对Chunk实体进行接收,长度大小为Chunk-Size
			 ****************************/

			for(int i = 0;i  
 





推荐阅读
  • 1、编写一个Java程序在屏幕上输出“你好!”。programmenameHelloworld.javapublicclassHelloworld{publicst ... [详细]
  • 本文基于Java官方文档进行了适当修改,旨在介绍如何实现一个能够同时处理多个客户端请求的服务端程序。在前文中,我们探讨了单客户端访问的服务端实现,而本篇将深入讲解多客户端环境下的服务端设计与实现。 ... [详细]
  • 本文探讨了如何利用 Android 的 Movie 类来展示 GIF 动画,并详细介绍了调整 GIF 尺寸以适应不同布局的方法。同时,提供了相关的代码示例和注意事项。 ... [详细]
  • 服务器虚拟化存储设计,完美规划储存与资源,部署高性能虚拟化桌面
    规划部署虚拟桌面环境前,必须先估算目前所使用实体桌面环境的工作负载与IOPS性能,并慎选储存设备。唯有谨慎估算贴近实际的IOPS性能,才能 ... [详细]
  • C# 中创建和执行存储过程的方法
    本文详细介绍了如何使用 C# 创建和调用 SQL Server 存储过程,包括连接数据库、定义命令类型、设置参数等步骤。 ... [详细]
  • UVa 11683: 激光雕刻技术解析
    自1958年发明以来,激光技术已在众多领域得到广泛应用,包括电子设备、医疗手术工具、武器等。本文将探讨如何使用激光技术进行材料雕刻,并通过编程解决一个具体的激光雕刻问题。 ... [详细]
  • 本文探讨了如何选择一个合适的序列化版本ID(serialVersionUID),包括使用生成器还是简单的整数,以及在不同情况下应如何处理序列化版本ID。 ... [详细]
  • 一、使用Microsoft.Office.Interop.Excel.DLL需要安装Office代码如下:2publicstaticboolExportExcel(S ... [详细]
  • 电商高并发解决方案详解
    本文以京东为例,详细探讨了电商中常见的高并发解决方案,包括多级缓存和Nginx限流技术,旨在帮助读者更好地理解和应用这些技术。 ... [详细]
  • HTTP(HyperTextTransferProtocol)是超文本传输协议的缩写,它用于传送www方式的数据。HTTP协议采用了请求响应模型。客服端向服务器发送一 ... [详细]
  • DVWA学习笔记系列:深入理解CSRF攻击机制
    DVWA学习笔记系列:深入理解CSRF攻击机制 ... [详细]
  • 本文探讨了在不解压的情况下,如何高效地从包含文本文件的.gz压缩文件中查找特定字符串的方法。通过利用特定的工具和技术,可以在保持文件压缩状态的同时,快速定位和检索所需信息,提高处理大规模数据集时的效率和性能。 ... [详细]
  • HDU 2537 键盘输入处理
    题目描述了一个名叫Pirates的男孩想要开发一款键盘输入软件,遇到了大小写字母判断的问题。本文提供了该问题的解决方案及实现方法。 ... [详细]
  • 本文由公众号【数智物语】(ID: decision_engine)发布,关注获取更多干货。文章探讨了从数据收集到清洗、建模及可视化的全过程,介绍了41款实用工具,旨在帮助数据科学家和分析师提升工作效率。 ... [详细]
  • 在腾讯云服务器上部署Nginx的详细指南中,首先需要确保安装必要的依赖包。如果这些依赖包已安装,可直接跳过此步骤。具体命令包括 `yum -y install gcc gcc-c++ wget net-tools pcre-devel zlib-devel`。接下来,本文将详细介绍如何下载、编译和配置Nginx,以确保其在腾讯云服务器上顺利运行。此外,还将提供一些优化建议,帮助用户提升Nginx的性能和安全性。 ... [详细]
author-avatar
苏格兰的秋天
一枚在创业路上坚持走下去的人。。。。
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有