直接进入主题。
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\r\n");
            // 开始拼接请求头
            sb.append("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n");
            sb.append("Connection: keep-alive\r\n");
            // Content-Length 跟 Content-Type 在POST请求时起很重要的作用,没有这两个请求头,就算你发送的请求实体,服务端也是解析不到请求实体的。
            sb.append("Content-Length: 7\r\n");
            sb.append("Content-Type: application/x-www-form-urlencoded\r\n");
            sb.append("Host: 127.0.0.1:8080\r\n");
            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\r\n");
            // 结束拼接请求头,然后拼接一个空行
            sb.append("\r\n");
            // 这里是请求实体,用于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
这三行是消息的报头,表示了内容的类型,内容的长度跟服务器的响应时间。
消息报头后有一行空行,然后就是响应的内容了,也就是服务端返回的数据。
来源:https://www.cnblogs.com/jellybean961/p/12448027.html