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

news2024/11/20 9:23:18

        在网络协议中,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类,在浏览器中地址栏请求该地址,看一下效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1442542.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑数据误删如何恢复?9 个Windows 数据恢复方案

无论您是由于软件或硬件故障、网络犯罪还是意外删除而丢失数据&#xff0c;数据丢失都会带来压力和令人不快。 如今的企业通常将其重要数据存储在云或硬盘上。但在执行其中任何一项操作之前&#xff0c;您很有可能会丢失数据。 数据丢失的主要原因是意外删除&#xff0c;任何…

Linux匿名管道

目录 1.原理 1.直接原理 2.本质原理 2.管道接口 3.管道中的四种情况 1.读写端正常&#xff0c;管道如果为空&#xff0c;读端就要堵塞 2.读写端正常&#xff0c;管道如果被写满&#xff0c;写端就要堵塞 3.读端正常&#xff0c;写端关闭&#xff0c;读端就会读到0&#…

Mysql Day03

多表设计 一对多 在多的一方添加外键约束&#xff0c;关联另外一方主键 一对一 任意一方添加外键约束&#xff0c;关联另外一方主键 多对多 建立第三张中间表&#xff0c;中间表至少包含两个外键&#xff0c;分别关联两方主键 idstu_idcourse_id 1 11 2 12313421524 案…

机器学习系列6-逻辑回归

重点&#xff1a; 1.逻辑回归模型会生成概率。 2. 对数损失是逻辑回归的损失函数。 3. 逻辑回归被许多从业者广泛使用。 # 1.逻辑回归&#xff1a;计算概率 **许多问题需要将概率估算值作为输出。逻辑回归是一种非常高的概率计算机制。** 实际上&#xff0c;您可以通过以下两种…

test222

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 磁盘满的本质分析 专栏&#xff1a;《Linux从小白到大神》 | 系统学习Linux开发、VIM/GCC/GDB/Make工具…

记:STM32F4参考手册-存储器和总线架构

STM32F4参考手册-存储器和总线架构 系统架构 主系统由32位多层AHB总线矩阵构成&#xff0c;可实现以下部分部分的互连&#xff1a; 八条主控总线&#xff1a; Cortex-M4F内核I总线、D总线和S总线 DMA1存储器总线 DMA2存储器总线 DMA2外设总线 以太网DMA总线 USB OTG HS DMA总线…

【Python中Selenium元素定位的各种方法】

1、元素定位操作&#xff1a; 2、创建浏览器驱动操作&#xff0c;导入By模块&#xff1a; from selenium import webdriver # 用于界面与浏览器互动 from selenium.webdriver.common.by import By # 用于元素定位 driver webdriver.Chrome() # 调用Chrome类&#xff0c;创…

【Java EE初阶十一】文件操作(IO)

1. 认识文件 所谓的文件是一个广义的概念&#xff0c;可以代表很多东西&#xff1b;在操作系统里面&#xff0c;会把很多的硬件设备和软件设备都抽象成“文件”&#xff0c;统一进行管理&#xff1b;但是大部分情况下&#xff0c;我们读到的文件&#xff0c;都是指硬盘的文件&a…

计算机网络第6章(应用层)

6.1、应用层概述 我们在浏览器的地址中输入某个网站的域名后&#xff0c;就可以访问该网站的内容&#xff0c;这个就是万维网WWW应用&#xff0c;其相关的应用层协议为超文本传送协议HTTP 用户在浏览器地址栏中输入的是“见名知意”的域名&#xff0c;而TCP/IP的网际层使用IP地…

如何让内网client通过公网地址访问内网server?

第一步&#xff0c;实现任意公网用户访问内网server。按教育网规矩&#xff0c;公网过来的流量要访问校内网的server必须从教育专线&#xff08;路由器接口G0/0/1)进入。 第二步&#xff0c;实现内网主机通过公网地址210.43.2.3能够访问内网server192.168.1.2&#xff0c;图中①…

动漫风博客介绍页面源码

动漫风博客介绍页面源码&#xff0c;HTML源码&#xff0c;图片背景有淡入切换特效 蓝奏云&#xff1a;https://wfr.lanzout.com/iIDZu1nrmjve

科研绘图-半小提琴图-

文章目录 前言1.软件安装-Origin 20222.绘制半小提琴图3.绘制径向条形图 前言 本文叙述记录的是一些科研绘图的实现方法&#xff0c;具体介绍从软件安装到实现图表绘制的详细过程。 1.软件安装-Origin 2022 Origin是一款具有丰富绘图功能的科研绘图软件&#xff0c;安装过程…

百卓Smart管理平台 uploadfile.php 文件上传漏洞(CVE-2024-0939)

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

基于opencv-python模板匹配的银行卡号识别(附源码)

目录 介绍 数字模板处理 银行卡图片处理 导入数字模板 模板匹配及结果 介绍 我们有若干个银行卡图片和一个数字模板图片&#xff0c;如下图 我们的目的就是通过对银行卡图片进行一系列图像操作使得我们可以用这个数字模板检测出银行卡号。 数字模板处理 首先我们先对数…

2024Node.js零基础教程(小白友好型),nodejs新手到高手,(六)NodeJS入门——http模块

047_http模块_获取请求行和请求头 hello&#xff0c;大家好&#xff0c;那第二节我们来介绍一下如何在这个服务当中来提取 HTT 请求报文的相关内容。首先先说一下关于报文的提取的方法&#xff0c;我在这个文档当中都已经记录好了&#xff0c;方便大家后续做一个快速的查阅。 …

Python速成篇(基础语法)下(新年快乐♥)

引言 一天不学编程手就痒&#xff0c;今天是除夕&#xff0c;学C艹vector的话就没时间出去玩了&#xff0c;所以就写写博客。今天要讲的内容是关于&#xff0c;list&#xff08;列表&#xff09;&#xff0c;tuple&#xff08;元组&#xff09;&#xff0c;字典&#xff08;di…

C#,巴都万数列(Padonve Number)的算法与源代码

1 巴都万数列&#xff08;Padovan Sequence&#xff09; 巴都万数列&#xff08;Padovan Sequence&#xff09;是一个整数数列。 首数个值为1, 1, 1, 2, 2, 3, 4, 5, 7, 9, 12, 16, 21, 28, 37 ... 此数列以建筑师理察巴都万命名&#xff0c;他的论文Dom&#xff08;1994年&a…

Select 选择器 el-option 回显错误 value

离谱 回显的内容不是 label 而是 value 的值 返回官方看说明&#xff1a; v-model的值为当前被选中的el-option的 value 属性值 value / v-model 绑定值有3种类型 boolean / string / number 根据自身代码猜测是&#xff1a;tableData.bookId 与 item.id 类型不一致导致 &…

C++入门学习(二十六)for循环

for (初始化; 条件; 递增/递减) { // 代码块 } 打印1~10&#xff1a; #include <iostream> using namespace std; int main() { for (int i 1; i < 10; i) { cout <<i<<endl; } return 0; } 打印九九乘法表&#xff1a; #include <iostream…

大模型基础架构的变革:剖析Transformer的挑战者(下)

上一篇文章中&#xff0c;我们介绍了UniRepLKNet、StripedHyena、PanGu-π等有可能会替代Transformer的模型架构&#xff0c;这一篇文章我们将要介绍另外三个有可能会替代Transformer的模型架构&#xff0c;它们分别是StreamingLLM、SeTformer、Lightning Attention-2&#xff…