记一次使用Socket模拟HTTP请求
Posted jellybean961
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次使用Socket模拟HTTP请求相关的知识,希望对你有一定的参考价值。
直接进入主题。
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议,HTTP是基于TCP/IP通信协议来传递数据。
Java中的正好有一个类可以实现TCP的传输与接收,那就是Socket。
首先要先起一个SpringBoot项目用于接收HTTP请求,Controller很简单,接收请求,并返回请求的内容
@RequestMapping("response") @Controller public class MainController { private static Logger LOG = LoggerFactory.getLogger(MainController .class); @RequestMapping("index.html") @ResponseBody public String index(HttpServletRequest request){ LOG.info("请求内容:"+requestContent(request)); return "请求内容:"+requestContent(request); } public String requestContent(HttpServletRequest request){ Map<String,String[]> map = request.getParameterMap(); Iterator<String> iterator = map.keySet().iterator(); StringBuilder sb = new StringBuilder(); while(iterator.hasNext()){ String key = iterator.next(); String[] args = map.get(key); String str = ""; for(String s:args){ str += s+","; } if(key.equals("sign")) { str = str.replaceAll(" ", "+"); } sb.append(key+"="+(str.length() > 0 ? str.substring(0,str.length() - 1) : "")); sb.append("&"); } if(sb.length() > 0) { return sb.substring(0, sb.length() - 1); } return sb.toString(); } }
在发送报文前先了解一下HTTP协议的报文结构。
一个完整的GET请求的请求报文是这样的: (来源Chrome)
GET /response/index.html HTTP/1.1 Host: 127.0.0.1:8080 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 Sec-Fetch-Dest: document
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9
接下来就是使用Socket来发送这样结构的报文,模拟一次HTTP请求。
public static void main(String[] args) { try { // 新建一个socket套接字,地址跟端口都指向springboot项目 Socket socket = new Socket("127.0.0.1", 8080); // 拼接http请求报文 StringBuilder sb = new StringBuilder(); // 拼接请求行 sb.append("GET http://127.0.0.1:8080/response/index.html?a=1 HTTP/1.1 "); // 开始拼接请求头 sb.append("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 "); sb.append("Connection: keep-alive "); // Content-Length 跟 Content-Type 在POST请求时起很重要的作用,没有这两个请求头,就算你发送的请求实体,服务端也是解析不到请求实体的。 sb.append("Content-Length: 7 "); sb.append("Content-Type: application/x-www-form-urlencoded "); sb.append("Host: 127.0.0.1:8080 "); sb.append("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 "); // 结束拼接请求头,然后拼接一个空行 sb.append(" "); // 这里是请求实体,用于POST测试 sb.append("b=2&c=3"); // 将拼接好的报文发送至服务端 socket.getOutputStream().write(sb.toString().getBytes()); System.out.println("发送数据"); System.out.println(sb.toString()); // 开启一个线程去接收服务端返回的数据 // 开线程的目的是服务端需要时间进行响应,所以直接获取inputstream是拿不到服务端返回的数据的,因为服务端还没返回,你就直接去获取,这样是拿不到的 new Thread(new Runnable() { @Override public void run() { while(true) { byte[] b = new byte[1024]; int off = 0;try { while(socket.getInputStream().read(b, off, b.length) != -1) { System.out.println("返回数据:"+new String(b,"UTF-8")); } } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } }).start(); socket.shutdownOutput(); } catch (IOException e) { e.printStackTrace(); } }
将上面的代码运行后得到
发送数据 GET http://127.0.0.1:8080/response/index.html?a=1 HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Connection: keep-alive Content-Length: 7 Content-Type: application/x-www-form-urlencoded Host: 127.0.0.1:8080 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 b=2&c=3 返回数据:HTTP/1.1 200 Content-Type: text/html;charset=UTF-8 Content-Length: 16 Date: Mon, 09 Mar 2020 04:13:41 GMT 请求内容:a=1
会发现一个问题,请求实体b=2&c=3是一起发送过去的,但是后端解析的时候没能解析出来,因为GET请求是不包含请求实体的。
把GET换成POST后就得到
发送数据 POST http://127.0.0.1:8080/response/index.html?a=1 HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Connection: keep-alive Content-Length: 7 Content-Type: application/x-www-form-urlencoded Host: 127.0.0.1:8080 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 b=2&c=3 返回数据:HTTP/1.1 200 Content-Type: text/html;charset=UTF-8 Content-Length: 24 Date: Mon, 09 Mar 2020 04:17:21 GMT 请求内容:a=1&b=2&c=3
后端能够正确的获取到了请求实体。
在服务端返回的数据,结构跟请求报文差不多。第一行是状态行,第一行后的
Content-Type: text/html;charset=UTF-8 Content-Length: 24 Date: Mon, 09 Mar 2020 04:17:21 GMT
这三行是消息的报头,表示了内容的类型,内容的长度跟服务器的响应时间。
消息报头后有一行空行,然后就是响应的内容了,也就是服务端返回的数据。
以上是关于记一次使用Socket模拟HTTP请求的主要内容,如果未能解决你的问题,请参考以下文章