自己的web服务器项目-request请求和response响应处理
yuyutoo 2024-10-12 01:23 6 浏览 0 评论
我们经常使用别人的服务器进行构建网站,现在我们就自己来写一个自己的服务来使用。
准备工作:下载所需的题材及文档
注:完整项目下载
一、request请求获取
1、了解request请求
在写服务器之前,我们需要知道客户端发送给我们哪些信息?以及要求我们返回哪些信息?经过测试我们能够知道用户客户端发送的信息有以下几点:
客户端发送到服务器端的请求消息,我们称之为请求(request),其实就是一个按照http协议的规则拼接而成的字符串,Request请求消息包含三部分: 请求行 消息报头 请求正文
第一部 请求行
格式:
Method Request-URI HTTP-Version CRLF
各部分分别为:
Method表示请求方法;一般为GET或者POST ;Request-URI是一个统一资源标识符; HTTP-Version表示请求的HTTP协议版本; CRLF表示回车和换行
例如:
GET /test.html HTTP/1.1
第二部 消息报头 http header
例如:
GET /test.html HTTP/1.1
Host: 127.0.0.1:9999
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,en;q=0.8,zh;q=0.5,en-US;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
第三部 请求正文 http body
请求头和请求正文之间是一个空行,这个行非常重要,它表示请求头已经结束,接下来的是请求正文。请求正文中可以包含客户提交的字符串信息
注意:在第二部分header和第三部分body之间有个空行,除非没有请求正文(如果你想要亲自看到效果,请参考:浏览器中GET和POST的区别),这是因为用户在浏览网页时提交给服务器的信息是不同的
2、实现
经过以上分析,我们就能够清楚的知道,客户端发送给服务器的请求,请求信息有使用的协议、请求的方法、请求的资源路径、请求的消息报头、判断请求的内容是否为静态资源、判断请求的内容是否为动态资源、判断是否为空请求,为了使用的方便,我们需要将其封装起来,总不能使用一次读取一次吧,这样做实在是太浪费系统资源与时间了,如下代码,就是一个接口类,用于获取客户端发送过来的属性
package com.sample.http; import java.util.Map; // http协议的请求 public interface HttpRequest { //获得请求的协议 public String getProtocol(); //获得请求的方法 public String getRequestMethod(); //获得请求的路径 public String getRequestPath(); //获得请求的消息报头 public Map<String,String> getRequestHeader(); //根据参数的名字获得请求带过来的参数值 public String getParameter(String parameterName); //判断当前请求的否是静态资源 public boolean isStaticResource(); //判断当前请求的否是动态资源 public boolean isDynamicResource(); //判断当前请求的否是为空请求(有些浏览器会自动发送空请求) public boolean isNullRequest(); }
有了接口类之后,我们就可以创建类进行实现,下面就是实现类,用于对各个方法进行处理:
package com.sample.http; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class HttpRequestImpl implements HttpRequest{ //客户端的Socket private Socket s; private InputStream is=null;//输入流 private BufferedReader br=null; private HashMap<String,String> hmHeader=new HashMap<String,String>();//消息报头 private HashMap<String,String> hmparameters=new HashMap<String, String>();//参数集合 private boolean isNullRequest=false;//是否为空请求,默认false private String protocol=null;//请求协议 private String requestMethod=null;//请求方法 private String requestPath=null;//请求路径 public HttpRequestImpl(Socket s) { this.s=s; getInfos();//调用方法 } private void getInfos()//定义一个方法,用于处理获取的客户端信息 { try { is=s.getInputStream(); br=new BufferedReader(new InputStreamReader(is)); //解析第一行 String str; str=br.readLine();//readLine使用回车换行判断一行是否结束 if(str==null) { isNullRequest=true; return; } parseRequestMethodPathProtocol(str);//调用自己创建在本类里边的方法处理第一行信息,方法在后面 //解析第二行---第八行 String header=null; //判断是否结束,如果结束就退出,这里的判断按较为饶人 //首先应该明确br.readLine()的内容,当为true是对应的情况 //也就是说当“”(中间没有空格)与br.readLine()相等时,就进入到while中 while(!"".equals((header=br.readLine()))){ parseRequestHeader(header); } //post和get if(br.ready())//post//POST提交方式判断,如果还有下一行就继续读取信息 { char[] buf=new char[1024]; int len=br.read(buf);//使用字节进行读取,因为这一行没有回车换行,readLine无法判断是否结束 String parameter=new String(buf,0,len); parseRequestParamByPost(parameter);//调用自己创建在本类里边的方法处理POST方式提交的正文信息 } else {//get,get参数的位置在第一行连接处 parseRequestParamByGet(requestPath);//调用自己创建在本类里边的方法处理GET方式提交的正文信息 } } catch (Exception e) { e.printStackTrace(); } } //GET方法处理 private void parseRequestParamByGet(String requestPath2) { String []str1=requestPath2.split("[?]");//使用“?”分割字符串 if(str1.length==2) { parseRequestParamByPost(str1[1]); } this.requestPath=str1[0]; } //POST方法处理 private void parseRequestParamByPost(String parameter) { String[] strs=parameter.split("&"); if(strs.length>=1) { for(String str:strs) { String [] sp=str.split("="); hmparameters.put(sp[0],sp[1]); } } } //解析第二行到第八行 private void parseRequestHeader(String header) throws Exception{ String[] headHost=header.split(": "); if(headHost.length!=2) { throw new Exception("消息报头异常,请重新提交"); } hmHeader.put(headHost[0],headHost[1]); } //解析第一行 private void parseRequestMethodPathProtocol(String str) throws Exception { String[] protocolMethodPath=new String[3];//由于第一行含有三个内容,分割后需要三个String存储 protocolMethodPath=str.split(" ");//使用空格分割 if(protocolMethodPath.length==3) { requestMethod=protocolMethodPath[0]; requestPath=protocolMethodPath[1]; protocol=protocolMethodPath[2]; } else { throw new Exception("首行参数不合适,请重新提交"); } } //获得请求的协议 public String getProtocol() { return protocol; } //获得请求的方法 public String getRequestMethod(){ return requestMethod; } //获得请求的路径 public String getRequestPath(){ return requestPath; } //获得请求的消息报头 public Map<String,String> getRequestHeader(){ return this.hmHeader; } //根据参数的名字获得请求带过来的参数值 public String getParameter(String parameterName){ return hmparameters.get(parameterName); } //判断当前请求的否是静态资源 public boolean isStaticResource(){ return true; } //判断当前请求的否是动态资源 public boolean isDynamicResource(){ return true; } //判断当前请求的否是为空请求(有些浏览器会自动发送空请求) public boolean isNullRequest(){ return isNullRequest; } }
以上内容是对客户端(浏览器)请求内容的处理,即如何进行包装客户端请求的信息,并且将其包装成一个统一的整体,既然能够获取浏览器的内容,那么,我们就必须采取一定的措施告诉浏览器我们找到了你想要的文件,并且将返回给客户端,下面我们就来实现如何返回给客户端想要的信息
二、response响应处理
1、了解response响应
服务器在接收和解释客户端的请求消息后,服务器会返回给客户端一个HTTP响应消息,我们称之为响应(response)。其实也是一个按照http协议的规则拼接而成的一个字符串
HTTP响应也是由三个部分组成,分别是: 响应状态行、消息报头、响应正文
第一部分 响应状态行
格式如下:
HTTP-Version Status-Code Reason-Phrase CRLF
例如:
HTTP/1.1 200 OK
各部分分别为:
HTTP-Version表示服务器HTTP协议的版本;
Status-Code表示服务器发回的响应状态代码;
Reason-Phrase表示状态代码的文本描述。
CRLF表示回车和换行
第二部分 消息报头
HTTP消息报头包括普通报头、请求报头、响应报头、实体报头这四大类。
每一个 报头域 都是由 名字+冒号+空格+值 组成,消息报头域的名字不区分大小写。它们的作用是描述 客户端或者服务器 的属性
1.普通报头:即可用于请求,也可用于响应,是作为一个整体而不是特定资源与事务相关联。
2.请求报头:允许客户端传递关于自身信息和希望的响应形式。
3.响应报头:允许服务器传递关于自身信息的响应。
4.实体报头:定义被传送资源的信息。即可用于请求,也可用于响应。
什么是 MIME Type?
首先,我们要了解浏览器是如何处理内容的。在浏览器中显示的内容有 HTML、有 XML、有 GIF、还有 Flash ……那么,浏览器是如何区分它们,决定什么内容用什么形式来显示呢?答案是 MIME Type,也就是该资源的媒体类型。媒体类型通常是通过 HTTP 协议,由 Web 服务器告知浏览器的,更准确地说,是通过响应的消息报头里面的属性 Content-Type 来表示的,例如:Content-Type: text/HTML表示内容是 text/HTML 类型,也就是超文本文件。为什么是“text/HTML”而不是“HTML/text”或者别的什么?MIME Type 不是个人指定的,是经过 ietf 组织协商,以 RFC 的形式作为建议的标准发布在网上的,大多数的 Web 服务器和用户代理都会支持这个规范 (顺便说一句,Email 附件的类型也是通过 MIME Type 指定的)。
通常只有一些在互联网上获得广泛应用的格式才会获得一个 MIME Type,如果是某个客户端自己定义的格式,一般只能以 application/x- 开头。XHTML 正是一个获得广泛应用的格式,因此,在 RFC 3236 中,说明了 XHTML 格式文件的 MIME Type 应该是 application/xHTML+XML。当然,处理本地的文件,在没有人告诉浏览器某个文件的 MIME Type 的情况下,浏览器也会做一些默认的处理,这可能和你在操作系统中给文件配置的 MIME Type 有关。比如在 Windows 下,打开注册表的“HKEY_LOCAL_MACHINESOFTWAREClassesMIMEDatabaseContent Type”主键,你可以看到所有 MIME Type 的配置信息
每个MIME类型由两部分组成,前面是数据的大类别,例如声音audio、图象image等,后面定义具体的种类。
常见的MIME类型
超文本标记语言文本 .html,.html text/html
普通文本 .txt text/plain
RTF文本 .rtf application/rtf
GIF图形 .gif image/gif
JPEG图形 .ipeg,.jpg image/jpeg
au声音文件 .au audio/basic
MIDI音乐文件 mid,.midi audio/midi,audio/x-midi
RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
MPEG文件 .mpg,.mpeg video/mpeg
AVI文件 .avi video/x-msvideo
GZIP文件 .gz application/x-gzip
TAR文件 .tar application/x-tar
第三部分 响应正文
响应正文就是服务器返回的资源的内容
2、实现
首先,我们需要进行抽象,即将浏览器想要的信息,即如下内容包装起来,如下所示,我们将其包装成一个接口,在抽象时我们必须认识到用户可能会犯的错误,所以尽量使用重载的方法进行避免,在下面的接口中,使用了重载进行处理部分方法:
package com.sample.http; import java.io.OutputStream; import java.io.PrintWriter; //http协议的响应 public interface HttpResponse { //获得一个指向客户端的字节流 public OutputStream getOutputStream()throws Exception; //获得一个指向客户端的字符流 public PrintWriter getPrintWriter()throws Exception; //设置响应的状态行 参数为String类型 public void setStatusLine(String statusCode); //设置响应的状态行 参数为int类型 public void setStatusLine(int statusCode); //设置响应消息报头 public void setResponseHeader(String hName,String hValue); //设置响应消息报头中Content-Type属性 public void setContentType(String contentType); //设置响应消息报头中Content-Type属性 并且同时设置编码 public void setContentType(String contentType,String charsetName); //设置CRLF 回车换行 \r\n public void setCRLF(); //把设置好的响应状态行、响应消息报头、固定空行这三部分写给浏览器 public void printResponseHeader(); //把响应正文写给浏览器 public void printResponseContent(String requestPath); }
在接口中,我们能够看到详细的解释,下面我们就来实现接口中的方法:
package com.sample.http; <pre name="code" class="java">import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.Socket; import com.sample.utils.ConfigUtils; import com.sample.utils.StatusCodeUtils; //http协议的响应 public class HttpResponseImpl implements HttpResponse { //声明初始变量 Socket s;//客户端socket OutputStream os;//输出端字节流 BufferedWriter bw;//输出端字符流 PrintWriter pw; StringBuffer sbuffer;//放状态行,\r\n , FileInputStream fis; File file; //构造器 public HttpResponseImpl(Socket s) { this.s=s; System.out.println("HttpRequestImpl(Socket s)"); os=null; bw=null; pw=null; sbuffer=new StringBuffer();//初始化,记得,否则以下的操作会遇见空指针异常 fis=null; file=null; getInfos(); } private void getInfos() { try { os=s.getOutputStream(); bw=new BufferedWriter(new OutputStreamWriter(os,"GBK")); pw=new PrintWriter(bw); } catch (Exception e) { e.printStackTrace(); } } //获得一个指向客户端的字节流 public OutputStream getOutputStream()throws Exception { return os; } //获得一个指向客户端的字符流 public PrintWriter getPrintWriter()throws Exception { return pw; } //设置响应的状态行 参数为String类型 public void setStatusLine(String statusCode) { String str=StatusCodeUtils.getStatusCodeValue(statusCode); //System.out.println(str+"------"+str.length()); sbuffer.append("HTTP/1.1 "+statusCode+" "+str); setCRLF(); } //设置响应的状态行 参数为int类型 public void setStatusLine(int statusCode) { setStatusLine(statusCode+"");//将int类型转化为String类型 } //设置响应消息报头 public void setResponseHeader(String hName,String hValue) { sbuffer.append(hName+": "+hValue); setCRLF(); } //设置响应消息报头中Content-Type属性 public void setContentType(String contentType) { setResponseHeader("Content-Type",contentType); } //设置响应消息报头中Content-Type属性 并且同时设置编码 public void setContentType(String contentType,String charsetName) {//text/html;charset=utf-8 setContentType(";charset= "+charsetName); } //设置CRLF 回车换行 \r\n public void setCRLF() { sbuffer.append("\r\n"); } //把设置好的响应状态行、响应消息报头、固定空行这三部分写给浏览器 public void printResponseHeader() { //设置setResponseLine,setResponseHeader,setResponseType String res=sbuffer.toString(); pw.print(res); pw.flush(); } //把响应正文写给浏览器 public void printResponseContent(String requestPath) { //响应正文 String getPath= requestPath;//客户请求的地址 String webHome=(new ConfigUtils()).getConfigValue("rootPath"); System.out.println("配置文件中目录:"+webHome);//输出从配置文件中获取的地址 file=new File(webHome+getPath); if(file.exists())//如果文件存在就执行 { try { fis=new FileInputStream(file); byte[] buf=new byte[1024]; int len=-1; while((len=fis.read(buf))!=-1) { //String str=buf.toString(); //bw.write(str);//字符流写过去是一个地址,因为写过去之后需要浏览器解析,如果是图片或者其他(图片或视频是字节流)的该怎么解析呢? //System.out.println(str); os.write(buf, 0, len); } bw.flush(); os.flush();//os要不要关??? } catch (IOException e) { e.printStackTrace(); }finally { try { if(bw!=null) bw.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
在Eclipse中写完以上代码我们会发现,其中有多处错误信息,其原因是我们没有进行创建以上代码所要求的类,现在我们进行创建,其使用方法请参见:java.util 类 Properties ,使用参考中的方法,我们能够进行对所需要的信息进行配置,在以上代码中使用的地方有两处,分别是【注意:这样做的好处是增减了项目的灵活性,用户能够在不查看代码的情况下随时更改配置文件等一些文件的信息,】:
(1)设置状态行处
//设置响应的状态行 参数为String类型 public void setStatusLine(String statusCode) { String str=StatusCodeUtils.getStatusCodeValue(statusCode); //System.out.println(str+"------"+str.length()); sbuffer.append("HTTP/1.1 "+statusCode+" "+str); setCRLF(); }
其中StatusCodeUtils类创建如下所示,而对于status_code.properties文件存放在下载的准备文件中的/webservlet/project/中,直接复制到项目中即可:
package com.sample.utils; import java.io.IOException; import java.io.InputStream; import java.util.Properties; public class StatusCodeUtils { private static Properties p; static { InputStream in=null; p=new Properties(); try { //读了xx.properties文件 in=StatusCodeUtils.class.getResourceAsStream("status_code.properties"); //放置到p中,即放properties文件中的key,value p.load(in); } catch (IOException e) { e.printStackTrace(); } finally { if(in!=null) try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } public static String getStatusCodeValue(String status) { return p.getProperty(status); } public static String getStatusCodeValue(int status) { return getStatusCodeValue(status+"");//没有空格 } /*public static void main(String[] args) {//输出测试 //Properties p=new Properties(); // p.setProperty("rootPath","ddd"); //System.out.println(p.get("rootPath")); System.out.println(getStatusCodeValue("304")); System.out.println(getStatusCodeValue("200")); }*/ } (2)响应正文处: //响应正文 String getPath= requestPath;//客户请求的地址 String webHome=(new ConfigUtils()).getConfigValue("rootPath"); System.out.println("配置文件中目录:"+webHome);//输出从配置文件中获取的地址 file=new File(webHome+getPath);
在响应正文中使用了ConfigUtils类进行了项目路径的获取,代码如下所示,对于config.properties(注意:此文件中文件路径应该注意,我使用的是Linux系统,文件结构是/home/***,而对于windows系统,目录结构为:"C://webapps/*****,最好在地址栏复制地址,写到配置中")文件也在准备文件中,请自行下载,然后复制到项目中:就是下面这个东西,路径配置合适,然后你就可以将自己的项目放在webapps目录下,让自己的电脑作为服务器供其他人访问自己的网站了
ConfigUtils路径配置类,用于获取项目文件目录位置
package com.sample.utils; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Properties; public class ConfigUtils { private static Properties p; static { InputStream in=null; OutputStream on=null; p=new Properties(); try { //读了xx.properties文件 in=ConfigUtils.class.getResourceAsStream("config.properties"); //放置到p中,即放properties文件中的key,value p.load(in); } catch (IOException e) { e.printStackTrace(); } finally { if(in!=null) try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } public static String getConfigValue(String config) { return p.getProperty(config); } public static void main(String[] args) {//输出测试 // Properties p=new Properties(); // p.setProperty("rootPath","ddd"); // System.out.println(p.get("rootPath")); System.out.println(getConfigValue("rootPath")); } }
到此为止,我们已经实现了服务器的主要任务,接受请求和处理请求,下面我们进行测试:
写一个测试类如下:
package com.sample.http; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class ServerTest { public static void main(String[] args) { //声明变量 ServerSocket ss=null; Socket s=null; boolean flag=true; try { int port=10002; System.out.println("Server Port:"+port); ss=new ServerSocket(port); //while(flag) { //接受客户端发送过来的Socket s=ss.accept(); HttpRequestImpl request=new HttpRequestImpl(s); // 用于测试收到的信息 System.out.println("获取的路径:"+request.getRequestPath()); System.out.println("获取的:"+request.getProtocol()); System.out.println("获取的:"+request.getRequestMethod()); System.out.println(request.getParameter("name")); System.out.println(request.getParameter("id")); Map<String,String> m=request.getRequestHeader(); Set<Entry<String,String>> set=m.entrySet(); Iterator it=set.iterator(); while(it.hasNext()) { Entry entry=(Entry<String, String> )it.next(); System.out.println(entry.getKey()+"----"+entry.getValue()); } //写响应给浏览器 /* * 封装: * */ //输出流 HttpResponseImpl response=new HttpResponseImpl(s); response.setStatusLine(200); response.setContentType("text/html"); response.printResponseHeader(); response.setCRLF(); response.printResponseHeader(); response.printResponseContent(request.getRequestPath()); //用于输出信息 } } catch (IOException e) { e.printStackTrace(); } } }
在浏览器中输入地址回车:http://127.0.0.1:10002/test.html?id=1212&name=suguniang ,能够看到浏览器解析后的界面,当其他电脑访问时(其他电脑指的是同一个域内的),只要将127.0.0.1修改为本地的ip地址即可
此时控制台上也输出相应的信息:
相关推荐
- 墨尔本一华裔男子与亚裔男子分别失踪数日 警方寻人
-
中新网5月15日电据澳洲新快网报道,据澳大利亚维州警察局网站消息,22岁的华裔男子邓跃(Yue‘Peter’Deng,音译)失踪已6天,维州警方于当地时间13日发布寻人通告,寻求公众协助寻找邓跃。华...
- 网络交友须谨慎!美国犹他州一男子因涉嫌杀害女网友被捕
-
伊森·洪克斯克(图源网络,侵删)据美国广播公司(ABC)25日报道,美国犹他州一名男子于24日因涉嫌谋杀被捕。警方表示,这名男子主动告知警局,称其杀害了一名在网络交友软件上认识的25岁女子。雷顿警...
- 一课译词:来龙去脉(来龙去脉 的意思解释)
-
Mountainranges[Photo/SIPA]“来龙去脉”,汉语成语,本指山脉的走势和去向,现比喻一件事的前因后果(causeandeffectofanevent),可以翻译为“i...
- 高考重要考点:range(range高考用法)
-
range可以用作动词,也可以用作名词,含义特别多,在阅读理解中出现的频率很高,还经常作为完形填空的选项,而且在作文中使用是非常好的高级词汇。...
- C++20 Ranges:现代范围操作(现代c++白皮书)
-
1.引言:C++20Ranges库简介C++20引入的Ranges库是C++标准库的重要更新,旨在提供更现代化、表达力更强的方式来处理数据序列(范围,range)。Ranges库基于...
- 学习VBA,报表做到飞 第二章 数组 2.4 Filter函数
-
第二章数组2.4Filter函数Filter函数功能与autofilter函数类似,它对一个一维数组进行筛选,返回一个从0开始的数组。...
- VBA学习笔记:数组:数组相关函数—Split,Join
-
Split拆分字符串函数,语法Split(expression,字符,Limit,compare),第1参数为必写,后面3个参数都是可选项。Expression为需要拆分的数据,“字符”就是以哪个字...
- VBA如何自定义序列,学会这些方法,让你工作更轻松
-
No.1在Excel中,自定义序列是一种快速填表机制,如何有效地利用这个方法,可以大大增加工作效率。通常在操作工作表的时候,可能会输入一些很有序的序列,如果一一录入就显得十分笨拙。Excel给出了一种...
- Excel VBA入门教程1.3 数组基础(vba数组详解)
-
1.3数组使用数组和对象时,也要声明,这里说下数组的声明:'确定范围的数组,可以存储b-a+1个数,a、b为整数Dim数组名称(aTob)As数据类型Dimarr...
- 远程网络调试工具百宝箱-MobaXterm
-
MobaXterm是一个功能强大的远程网络工具百宝箱,它将所有重要的远程网络工具(SSH、Telnet、X11、RDP、VNC、FTP、MOSH、Serial等)和Unix命令(bash、ls、cat...
- AREX:携程新一代自动化回归测试工具的设计与实现
-
一、背景随着携程机票BU业务规模的不断提高,业务系统日趋复杂,各种问题和挑战也随之而来。对于研发测试团队,面临着各种效能困境,包括业务复杂度高、数据构造工作量大、回归测试全量回归、沟通成本高、测试用例...
- Windows、Android、IOS、Web自动化工具选择策略
-
Windows平台中应用UI自动化测试解决方案AutoIT是开源工具,该工具识别windows的标准控件效果不错,但是当它遇到应用中非标准控件定义的UI元素时往往就无能为力了,这个时候选择silkte...
- python自动化工具:pywinauto(python快速上手 自动化)
-
简介Pywinauto是完全由Python构建的一个模块,可以用于自动化Windows上的GUI应用程序。同时,它支持鼠标、键盘操作,在元素控件树较复杂的界面,可以辅助我们完成自动化操作。我在...
- 时下最火的 Airtest 如何测试手机 APP?
-
引言Airtest是网易出品的一款基于图像识别的自动化测试工具,主要应用在手机APP和游戏的测试。一旦使用了这个工具进行APP的自动化,你就会发现自动化测试原来是如此简单!!连接手机要进行...
- 【推荐】7个最强Appium替代工具,移动App自动化测试必备!
-
在移动应用开发日益火爆的今天,自动化测试成为了确保应用质量和用户体验的关键环节。Appium作为一款广泛应用的移动应用自动化测试工具,为测试人员所熟知。然而,在不同的测试场景和需求下,还有许多其他优...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)