热门标签 | 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  
 





推荐阅读
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 配置IPv4静态路由实现企业网内不同网段用户互访
    本文介绍了通过配置IPv4静态路由实现企业网内不同网段用户互访的方法。首先需要配置接口的链路层协议参数和IP地址,使相邻节点网络层可达。然后按照静态路由组网图的操作步骤,配置静态路由。这样任意两台主机之间都能够互通。 ... [详细]
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
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社区 版权所有