从Socket中解析Http协议实现通信

        在网络协议中,Socket是连接应用层和运输层的中间层,主要作用为了通信。Http协议是应用层上的封装协议。我们可以通过Http协议的规范解析Socket中数据,完成Http通信。

        首先,我们先回顾一下Http协议的规范。主要复习一下,请求与响应报文格式,方便我们解析Socket中数据。请求报文格式具体如下图:

从Socket中解析Http协议实现通信_第1张图片

         响应报文格式具体如下图:

从Socket中解析Http协议实现通信_第2张图片

         了解Http请求和响应的报文格式后,可准备编写代码了。Java的Socket支持BIO、NIO等IO模型,我以下的代码使用BIO阻塞模式实现通信,具体代码如下:

        HttpBioServer类主要负责开启Socket服务端监听,当有客户端连接接入后,读取客户端数据,将客户端数据交给另一个线程处理。另一个线程会调用http(),http()会解析Http请求的数据,对数据进行一些操作后,再封装一个响应体返回给客户端。


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * 〈一句话功能简述〉
* 〈Bio服务端〉 * * @author hanxiaozhang * @create 2023/6/20 * @since 1.0.0 */ public class HttpBioServer { public static void main(String[] args) throws Exception { ServerSocket server = new ServerSocket(9090, 20); while (true) { // 阻塞1 Socket client = server.accept(); // System.out.println(client.getInetAddress()); // System.out.println(client.getLocalPort()); System.out.println("client connect success,client port is " + client.getPort()); new Thread(new Runnable() { @Override public void run() { try { http(client); client.close(); System.out.println("client close"); } catch (IOException e) { e.printStackTrace(); } } }).start(); } } /** * Http协议 * * @param client * @throws IOException */ private static void http(Socket client) throws IOException { // 读取输入流中数据 ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream(); try { InputStream in = client.getInputStream(); int len = 0; byte[] buf = new byte[1024]; // 每次读取 1024 字节,知道读取完成 while ((len = in.read(buf)) != 0) { byteArrayOut.write(buf, 0, len); if (in.available() == 0) { break; } } byte[] bytes = byteArrayOut.toByteArray(); RequestEntity request = new RequestEntity(); request.byteToRequest(bytes); byte[] responseBytes = handler(request); OutputStream ops = client.getOutputStream(); ops.write(responseBytes); ops.flush(); } finally { byteArrayOut.close(); } } private static byte[] handler(RequestEntity request) { System.out.println("request is " + request); Map headers = new HashMap<>(4); headers.put("Content-Type", "text/plain"); String body = "success"; // 假装处理一些逻辑 ResponseEntity response = new ResponseEntity(200, "OK", headers, body); byte[] responseBytes = response.responseToBytes(request); System.out.println("response is " + response); return responseBytes; } }

        RequestEntity类主要是存储Http请求解析后的数据,并且包含了一个将请求数据转换为RequestEntity类数据的方法。


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * 〈一句话功能简述〉
* 〈请求实体〉 * * @author hanxiaozhang * @create 2023/6/25 * @since 1.0.0 */ @Data @Builder @AllArgsConstructor @NoArgsConstructor @Accessors(chain = true) public class RequestEntity { /** * 请求行 */ private String requestLine; /** * 请求方法 */ private String method; /** * Url */ private String url; /** * 版本协议 */ private String requestAndVersion; /** * header */ private Map headers; /** * 报文内容 */ private String body; /** * 字节数组转换request实体 * * @param bytes * @return */ public void byteToRequest(byte[] bytes) { // \r\n连续出现两次的情况认为首部结束,剩下是主体部分 int flag = 0; // 是否为body内容 boolean isBody = false; char temp; StringBuffer headerSb = new StringBuffer(), bodySb = new StringBuffer(); Map headers = new HashMap<>(16); // 解析请求报文头和请求body for (int i = 0; i < bytes.length; i++) { if (isBody) { bodySb.append((char) bytes[i]); } else { temp = (char) bytes[i]; if (temp == '\r' || temp == '\n') { flag++; } else { flag = 0; } if (flag == 4) { isBody = true; } headerSb.append(temp); } } // 解析请求行 String[] lines = headerSb.toString().split("\r\n"); String requestLine = lines[0]; String[] requestLines = requestLine.split("\\s"); this.setRequestLine(requestLine) .setMethod(requestLines[0]) .setUrl(requestLines[1]) .setRequestAndVersion(requestLines[2]); // 解析请求header for (int i = 1; i < lines.length; i++) { if (lines[i] != "") { String[] header = lines[i].split(": "); headers.put(header[0], header[1]); } } this.setHeaders(headers) .setBody(bodySb.toString()); } }

        ResponseEntity类主要是存储Http需要响应的数据,并且包含了一个将ResponseEntity类数据转换成Http响应的方法。

import com.hanxiaozhang.utils.StringUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.HashMap;
import java.util.Map;

/**
 * 〈一句话功能简述〉
* 〈〉 * * @author hanxiaozhang * @create 2023/6/25 * @since 1.0.0 */ @Data @Builder @AllArgsConstructor @NoArgsConstructor @Accessors(chain = true) public class ResponseEntity { public ResponseEntity(Integer stateCode, String reason, Map headers, String body) { this.stateCode = stateCode; this.reason = reason; this.headers = headers; this.body = body; } /** * 状态 */ private String stateLine; /** * 版本协议 */ private String requestAndVersion; /** * 状态码 */ private Integer stateCode; /** * 原因 */ private String reason; /** * 响应header */ private Map headers; /** * 响应内容 */ private String body; public byte[] responseToBytes(RequestEntity request) { // 处理状态行 StringBuilder sb = new StringBuilder(); this.requestAndVersion = request.getRequestAndVersion(); this.stateLine = request.getRequestAndVersion() + " " + stateCode + " " + reason; sb.append(stateLine); sb.append("\r\n"); // 处理响应header Map tempHeaders = new HashMap<>(16); tempHeaders.putAll(request.getHeaders()); if (this.headers != null && !this.headers.isEmpty()) { tempHeaders.putAll(this.headers); } if (StringUtil.isNotBlank(this.body)) { tempHeaders.put("Content-Length", String.valueOf(this.body.length())); } tempHeaders.forEach((k, v) -> { sb.append(k + ": " + v + "\r\n"); }); sb.append("\r\n"); // 处理响应body if (StringUtil.isNotBlank(this.body)) { sb.append(this.body); } return sb.toString().getBytes(); } }

        最后,我们启动HttpBioServer类,在浏览器中地址栏请求该地址,看一下效果:

从Socket中解析Http协议实现通信_第3张图片

你可能感兴趣的:(http,网络,后端,java)