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

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

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

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

         了解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;

/**
 * 〈一句话功能简述〉<br>
 * 〈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<String, String> 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;

/**
 * 〈一句话功能简述〉<br>
 * 〈请求实体〉
 *
 * @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<String, String> 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<String, String> 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;

/**
 * 〈一句话功能简述〉<br>
 * 〈〉
 *
 * @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<String, String> 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<String, String> 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<String, String> 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类,在浏览器中地址栏请求该地址,看一下效果:

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hanxiaozhang2018

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值