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





推荐阅读
  • 优化Nginx中PHP-FPM模块配置以提升性能
    通过调整Nginx与PHP-FPM之间的配置,可以显著提高Web服务器处理PHP请求的速度和效率。本文将详细介绍如何针对不同的应用场景优化PHP-FPM的各项关键参数。 ... [详细]
  • 本文介绍了如何在Ubuntu 16.04系统上配置Nginx服务器,以便能够通过网络访问存储在服务器上的图片资源。这解决了在网页开发中需要使用自定义在线图标的需求。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文详细介绍了为何需要进行端口转发,尤其是从标准 HTTP 80 端口到 Tomcat 默认 8080 端口的必要性,以及如何通过 Nginx 实现这一功能。同时,还将探讨 Nginx 在不同场景下的多种端口转发策略。 ... [详细]
  • 本文记录了作者在尝试启用IIS的Gzip压缩功能时遇到的挑战,特别是当企业内部网络使用ISA服务器作为代理时的问题。文章详细描述了问题的发现过程、解决步骤以及最终的解决方案。 ... [详细]
  • 本文介绍如何在 Android 中通过代码模拟用户的点击和滑动操作,包括参数说明、事件生成及处理逻辑。详细解析了视图(View)对象、坐标偏移量以及不同类型的滑动方式。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文探讨了 Spring Boot 应用程序在不同配置下支持的最大并发连接数,重点分析了内置服务器(如 Tomcat、Jetty 和 Undertow)的默认设置及其对性能的影响。 ... [详细]
  • 理解远程服务调用:RPC与HTTP
    本文深入探讨了远程服务调用中的两种主流技术——RPC(远程过程调用)与HTTP(超文本传输协议),分析了它们的工作原理、特点及适用场景。 ... [详细]
  • 本文详细介绍了如何通过 `vue.config.js` 文件配置 Vue CLI 的打包和代理设置,包括开发服务器配置、跨域处理以及生产环境下的代码压缩和资源压缩。 ... [详细]
  • CSV 文件的存取
    CSV文件介绍CSV(Comma-SeparatedValues),中文通常叫做逗号分割值。CSV文件由任意数目的记录(行& ... [详细]
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社区 版权所有