一、Http协议入门
- 什么是http协议?
http协议:超文本传输协议, 是对浏览器客户端和服务器端之间数据传输的格式规范。
http无状态协议:客户端向服务器端发送请求,没有事务管理。
http协议本质基于Socket的tcp协议,tcp协议是面向于连接,需要三次握手,基于流传输,底层使用二进制。 - 如何使用工具查看http协议?
使用F12进入浏览器开发者工具,进入到“网络”或者“Network” 监听请求,查看详情。
二、Http请求
- 请求行:
包括协议版本、请求资源、请求方式。- 协议版本:
http1.0:当前浏览器客户端与服务器端建立连接之后,只能发送一次请求,一次请求之后连接关闭。
http1.1:当前浏览器客户端与服务器端建立连接之后,可以在一次连接中发送多次请求。(基本都使用1.1) - 请求资源:
URL: 统一资源定位符:http://localhost:8080/myweb/index.html ,只能定位互联网资源,是URI的子集。
URI: 统一资源标记符:/jump/toIndex。用于标记任何资源,可以是本地文件系统,局域网的资源(//XX.XX.XX.XX/myweb/index.html ),也可以是互联网。 - 请求方式
常见的请求方式:GET 、POST、HEAD、TRACE、PUT、CONNECT、DELETE。
常用的请求方式:GET和POST。
表单提交:
GET和POST的区别:- GET方式提交
- 地址栏(URI)会跟上参数数据,以?开头,多个参数之间以&分,例如:/jump/toIndex?name=chauncy&passowrd=123456
- GET提交参数数据有限制,不超过1KB。
- GET方式不适合提交敏感密码。
- 注意: 浏览器直接访问的请求,默认提交方式是GET方式.
- POST方式提交
- 参数不会跟着URI后面,而是跟在请求的实体内容中,没有“?开头,多个参数之间以&分割”。
- POST提交的参数数据没有限制。
- POST方式提交敏感数据。
- GET方式提交
- 协议版本:
- 请求头:
Accept:image/webp,image/,/*;q=0.8 – 浏览器接受的数据类型
Accept-Encoding:gzip, deflate, sdch, br --浏览器接受的数据压缩格式
Accept-Language:zh-CN,zh;q=0.8 --浏览器接受的语言
Connection:keep-alive --浏览器跟服务器连接状态。close: 连接关闭
Cookie:BAIDUID=5A9A6A630F07903A59CE5AC97F29183C:FG=1; PSTM=1554778385; delPer=0; BDSVRTM=0; BD_HOME=0; H_PS_PSSID=1464_28825_21091_28768_28720_28558_28834_28584_28603_28625_28605; BIDUPSID=BD141F28DA26F67BD14281927E9DD216 --浏览器保存的cookie信息
DNT:1 --DO NOT TRACK的缩写,要求服务器程序不要跟踪记录用户信息。DNT: 1 (开启DNT) DNT: 0 (关闭DNT)火狐,safari,IE9都支持这个头域,并且于2011年3月7日被提交至IETF组织实现标准化。
Host:www.baidu.com --(必须的)当前请求访问的目标地址(主机:端口)
Referer:https://www.baidu.com/ --当前请求来自于哪里
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0 --浏览器类型
Date: Tue, 09 Apr 2019 11:05:21 GMT – 请求发出的时间
If-Modified-Since: Tue, 09 Apr 2019 11:05:21 GMT --浏览器最后的缓存时间 - 请求体(只有POST提交的参数会放到实体内容中)
- HttpServletRequest对象:
HttpServletRequest对象作用是用于获取请求数据。
核心的API:- 请求行:
request.getMethod(); 请求方式
request.getRequetURI() / request.getRequetURL() 请求资源
request.getProtocol() 请求http协议版本 - 请求头:
request.getHeader(“名称”) 根据请求头获取请求值
request.getHeaderNames() 获取所有的请求头名称 - 请求体:
request.getInputStream() 获取实体内容数据
- 请求行:
- 什么是时间戳?
http协议状态码为304:表示从本地缓存读取,不是向服务器发送请求,状态200表示请求服务器成功,从服务器中读取。
怎么实现不从缓存中读取?解决办法:在URL请求地址后面加上一个实现戳。
浏览器机制,通过URL请求地址后面加的参数判断地址有没有缓存过,缓存过就读取本地,没缓存过就从服务器读取。
很多网站在发布版本之前,都会在在js、css的URL请求地址后面加上一个时间戳进行版本更新,防止发布后用户不清除缓存资源不同步问题。 - web安全之防盗链/防止非法链接:
防盗链的思路:通过请求头中的Referer值进行判断过滤,如果不等于本站域名说明就是其他网站来源在请求。
代码实现:<filter> <filter-name>RefererFilter</filter-name> <filter-class>chauncy.filter.RefererFilter</filter-class> </filter> <filter-mapping> <filter-name>RefererFilter</filter-name> <url-pattern>/static/*</url-pattern> </filter-mapping>
package chauncy.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @classDesc: 功能描述(防止盗用下载过滤器) * @author: ChauncyWang * @version: 1.0 */ public class RefererFilter implements Filter{ public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest=(HttpServletRequest) request; HttpServletResponse httpResponse=(HttpServletResponse) response; //每次的请求来源 String referer = httpRequest.getHeader("referer"); //获取请求地址 String serverName = httpRequest.getServerName(); System.out.println("referer:"+referer+"-----serverName:"+serverName); //referer == null表示用户直接通过链接访问资源 if(referer == null || !referer.contains(serverName)){ httpRequest.getRequestDispatcher("/static/error.png").forward(httpRequest, httpResponse); return; } chain.doFilter(httpRequest, httpResponse); } public void destroy() { } }
三、Http响应
- 响应行:
- 协议版本
- 状态码: 服务器处理请求的结果(状态)
常见状态:
200 :表示请求处理完成并完美返回
302: 重定向,表示请求需要进一步细化。
404: 表示客户访问的资源找不到。
500: 表示服务器的资源发送错误。(服务器内部错误)
304: 读取本地缓存。
- 响应头:
Location: https://www.baidu.com/ --表示重定向的地址,该头和302的状态码一起使用。
Accept-Ranges:bytes --用于告知客户端服务器是否能处理范围请求,以指定获取服务器端某个部分的资源。可指定的字段值有两种,可处理范围请求时指定其为bytes,反之则指定其为none.
Cache-Control:max-age=315360000 --max-age在经过指定时间后将过期,no-cache表示客户端不缓存。
Connection:Keep-Alive --表示服务器和浏览器的连接状态。close:关闭连接 keep-alive:保存连接
Content-Length:7877 --表示服务器发送给浏览器的数据长度
Content-Language: zh-cn --表示服务器支持的语言
Content-Type:image/png --表示服务器发送给浏览器的数据类型及内容编码
Content-Encoding: gzip --表示服务器发送给浏览器的数据压缩类型
Content-Disposition: attachment; filename=aaa.zip --表示告诉浏览器以下载方式打开资源(下载文件时用到)
Date:Tue, 09 Apr 2019 08:50:53 GMT --Date头域表示消息发送的时间,缓存在评估响应的新鲜度时要用到,时间的描述格式由RFC822定义。
Etag:“1ec5-502264e2ae4c0” --可以将ETag看做是一个可以与Web资源关联的记号(token),如果ETag没改变,则返回状态304然后不返回
Expires:Fri, 06 Apr 2029 08:50:53 GMT --指示资源什么时候过期的时间值(GMT时间),在指定的过期时间前,浏览器可以直接使用自身缓存的版本,而不用向服务器发请求,大大减轻服务器压力,-1表示通知浏览器不进行缓存
Last-Modified:Wed, 03 Sep 2014 10:00:27 GMT --表示服务器资源的最后修改时间
Server:Apache --表示服务器的类型
Refresh: 1;url=https://www.baidu.com/ --表示定时刷新
Set-Cookie:SS=Q0=5Lb_nQ; path=/search --表示服务器发送给浏览器的cookie信息(会话管理用到) - 响应体(服务器响应给客户端的实体内容)
- HttpServletResponse对象:
HttpServletResponse对象修改响应信息:- 响应行:
response.setStatus() 设置状态码 - 响应头:
response.setHeader(“name”,“value”) 设置响应头 - 实体内容:
response.getWriter().writer(); 发送字符实体内容
response.getOutputStream().writer() 发送字节实体内容
- 响应行:
四、Https与Http
- https与http区别?
- https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用。
- http 是超文本传输协议,信息是明文传输(抓包可以抓到),https 则是具有安全性的 ssl 加密传输协议。
- http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
- http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。
- https工作原理?
我们都知道 HTTPS 能够加密信息,以免敏感信息被第三方获取,所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用 HTTPS 协议。
客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤,如图所示。
1. 客户使用 https 的 URL 访问 Web 服务器,要求与 Web 服务器建立 SSL 连接。
2. Web 服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
3. 客户端的浏览器与 Web 服务器开始协商 SSL 连接的安全等级,也就是信息加密的等级。
4. 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
5. Web 服务器利用自己的私钥解密出会话密钥。
6. Web 服务器利用会话密钥加密与客户端之间的通信。
- https优缺点?
虽然说 HTTPS 有很大的优势,但其相对来说,还是存在不足之处的:
1. HTTPS 协议握手阶段比较费时,会使页面的加载时间延长近 50%,增加 10% 到 20% 的耗电;
2. HTTPS 连接缓存不如 HTTP 高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响;
3. SSL 证书(例如:青云证书)需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。
4. SSL 证书通常需要绑定 IP,不能在同一 IP 上绑定多个域名,IPv4 资源不可能支撑这个消耗。
5. HTTPS 协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。最关键的,SSL 证书的信用链体系并不安全,特别是在某些国家可以控制 CA 根证书的情况下,中间人攻击一样可行。 - web安全与防攻击:
- XSS,脚本注入攻击:使用过滤器将所有提交的参数value值,转换成html代码执行。
- CSRF(跨站请求伪造,模拟http请求):企业当中会有白名单和黑名单,判断请求头来源。
- 上传漏洞 exe
- SQL注入
- 使用https 安全性高(走的是ssl+工信部颁布的证书,加密传输)、效率低
五、Http请求工具
- 客户端模拟http请求工具:
Postmen(谷歌插件)、RestClient等。 - 服务器模拟http请求工具:
httpclient、HttpURLConnection等。
短信轰炸机的实现原理:就是将互联网上的一些短信接口(http协议)统计好,使用httpclient模拟发送。
代码实现:- 服务器的POST请求接收接口:
package chauncy.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/UserFormServlet") public class UserFormServlet extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String userName=req.getParameter("userName"); String pwd=req.getParameter("pwd"); //自定义请求头 String token = req.getHeader("token"); System.out.println("userName:"+userName+"-----pwd:"+pwd+"-----token:"+token); resp.getWriter().write("success!"); } }
- httpclient模拟http请求代码:
package chauncy.httprequestutil; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.ParseException; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; /** * @classDesc: 功能描述(服务器模拟http请求工具httpclient实现) * @author: ChauncyWang * @version: 1.0 */ public class HttpClientUtil { public static void main(String[] args) throws ParseException, IOException { //get("https://www.baidu.com"); post("http://localhost/UserFormServlet"); } /** * * @methodDesc: 功能描述(模拟发送GET请求) * @author: ChauncyWang * @param: @param url * @param: @throws ParseException * @param: @throws IOException * @returnType: void */ static public void get(String url) throws ParseException, IOException{ //创建一个默认连接 CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); CloseableHttpResponse httpResponse = httpClient.execute(httpGet); //获取状态码 int statusCode = httpResponse.getStatusLine().getStatusCode(); if(statusCode == 200){ HttpEntity entity = httpResponse.getEntity(); String result = EntityUtils.toString(entity,"utf-8"); System.out.println("result:"+result); } } /** * * @methodDesc: 功能描述(模拟发送POST请求) * @author: ChauncyWang * @param: @param url * @param: @throws ClientProtocolException * @param: @throws IOException * @returnType: void */ static public void post(String url) throws ClientProtocolException, IOException{ //创建一个默认连接 CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost(url); httpPost.setHeader("token","13724912021304102734"); List<NameValuePair> formParams = new ArrayList<NameValuePair>(); formParams.add(new BasicNameValuePair("userName", "ChauncyWang")); formParams.add(new BasicNameValuePair("pwd", "123456")); UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(formParams,"utf-8"); httpPost.setEntity(urlEncodedFormEntity); CloseableHttpResponse httpResponse = httpClient.execute(httpPost); //获取状态码 int statusCode = httpResponse.getStatusLine().getStatusCode(); if(statusCode == 200){ HttpEntity entity = httpResponse.getEntity(); String result = EntityUtils.toString(entity,"utf-8"); System.out.println("result:"+result); } } }
- 服务器的POST请求接收接口:
- 前端模拟http请求工具:
使用Ajax(Asynchronous Javascript And XML,异步 JavaScript 和 XML)模拟请求。
ajax请求跨域问题:$.ajax({ type : 'post', dataType : "text", url : "http://localhost/UserFormServlet", data : "userName=ChauncyWang&sex=male", success : function(msg) { alert(msg); } });
跨域原因产生:在当前域名请求网站中,默认不允许通过ajax请求发送其他域名。
1. 问题:浏览器跨域请求,会在开发者工具的控制台报错:“Access to XMLHttpRequest at ‘http://www.chauncywang1.com/A/UserFormServlet1’ from origin ‘http://www.chauncywang2.com’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.”,请求正常显示200,但是没有返回结果。
2. 原因:在浏览器当中有一个规范来保证安全性,在本域名下的任何ajax请求地址,只要不与当前域名一致,都会抛出以上的异常,ajax请求正常造成,但是ajax回调不会执行,这就是ajax请求跨域问题产生的原因。
XMLHttpRequest cannot load 跨域问题解决办法:- 使用后台response添加header:
较多的小企业采用此种方式解决ajax跨域请求,后台response添加header,response.setHeader(“Access-Control-Allow-Origin”, “*”); 支持所有网站。
代码实现:package chauncy.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/UserFormServlet") public class UserFormServlet extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String userName=req.getParameter("userName"); String pwd=req.getParameter("pwd"); //自定义请求头 String token = req.getHeader("token"); System.out.println("userName:"+userName+"-----pwd:"+pwd+"-----token:"+token); //以下这句话只针对本请求可以支持跨域,如果想让所有请求都能跨域就要在Filter中使用这句话。 resp.setHeader("Access-Control-Allow-Origin", "*");//第二个参数:*表示允许所有网站,前端可以跨域访问,也可固定写死某个网站URL地址。 resp.getWriter().write("success!"); } }
- 使用JSONP:
很少使用到JSONP解决ajax跨域请求的问题,JSONP的缺点:JSONP只支持get请求不支持psot请求。- 前端代码:
$.ajax({ type : "POST",//虽然定义请求类型为post,但是通过观察实际请求行可知其为get请求,所以jsonp底层发送的是get请求 dataType : "jsonp",//数据类型为jsonp jsonp : "jsonpCallback",//服务端用于接收callback调用的function名的参数 url : "http://www.chauncywang2.com/B/UserFormServlet",//另一个网站的地址 data: "userName=张三&sex=male", success : function(data) { alert(data.result); }, error : function() { alert('fail'); } });
- 后台代码
package chauncy.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.alibaba.fastjson.JSONObject; @WebServlet("/UserFormServlet") public class UserFormServlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("使用Get请求访问,即将跳转到POST请求"); this.doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String userName=req.getParameter("userName"); String pwd=req.getParameter("pwd"); //自定义请求头 String token = req.getHeader("token"); System.out.println("userName:"+userName+"-----pwd:"+pwd+"-----token:"+token); /* 1.使用后台response添加header解决ajax请求跨域问题 * //以下这句话只针对本请求可以支持跨域,如果想让所有请求都能跨域就要在Filter中使用这句话。 * resp.setHeader("Access-Control-Allow-Origin", "*");//第二个参数:*表示允许所有网站,前端可以跨域访问,也可固定写死某个网站URL地址。 */ //2.使用jsonp解决ajax请求跨域问题 String jsonpCallback = req.getParameter("jsonpCallback"); JSONObject jsonObject = new JSONObject(); jsonObject.put("result", "success"); //返回形式,必须加上前台获取的参数,而且必须是JsonObject对象 resp.getWriter().write(jsonpCallback+"("+jsonObject.toString()+")"); } }
- 前端代码:
- 搭建网关系统,使用接口网关:
较多的大型企业解决ajax跨域问题是搭建网关系统,使用nginx转发。 - 使用后台服务进行转发:
先访问到本地的controller再使用httpclient进行转发,会发送两次请求,不靠谱,使用的很少,因为非常占宽带,不过还是有优点:安全性高不会暴露接口。
- 使用后台response添加header:
- http抓包工具:
fiddler、wireshark等。