用Netty手写Http/Https服务器

news2024/12/25 23:54:15

Netty是一个以事件驱动的异步通信网络框架,可以帮助我们实现多种协议的客户端和服务端通信,话不多说,上代码,需要引入下方依赖

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.42.Final</version>
        </dependency>

        <dependency>
            <groupId>org.msgpack</groupId>
            <artifactId>msgpack</artifactId>
            <version>0.6.12</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.4</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.8</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.49</version>
            <type>jar</type>
            <scope>compile</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.49</version>
            <type>jar</type>
            <scope>compile</scope>
            <optional>true</optional>
        </dependency>

1.Server

package http;

import constant.Constant;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;

import java.security.cert.CertificateException;

public class HttpServer {
    // 通过nio方式来接收连接和处理连接
    private static EventLoopGroup group = new NioEventLoopGroup();
    
    // 服务端引导类
    private static ServerBootstrap b = new ServerBootstrap();
    
    // 是否开启SSL模式
    public static final boolean SSL = false;

    // Netty创建全部都是实现自AbstractBootstrap,客户端的是Bootstrap,服务端的则是ServerBootstrap
    public static void main(String[] args) throws Exception {
        final SslContext sslContext;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslContext = SslContextBuilder.forServer(ssc.certificate(),
                    ssc.privateKey()).build();
            
        } else {
            sslContext = null;
        }
        
        try {
            b.group(group)
                    .channel(NioServerSocketChannel.class)
                    // 设置过滤器
                    .childHandler(new ServerHandlerInit(sslContext));
            // 异步进行绑定
            ChannelFuture f = b.bind(Constant.DEFAULT_PORT);
            // 给ChannelFuture 增加监听器
            f.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    System.out.println("绑定端口已成功....");
                }
            });
            System.out.println("服务端启动成功,端口是:" + Constant.DEFAULT_PORT);
            System.out.println("服务器启动模式: " + (SSL ? "SSL安全模式" : "普通模式"));
            // 监听服务器关闭监听
            ChannelFuture closeFuture = f.channel().closeFuture().sync();
            closeFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    System.out.println("服务器已经关闭....");
                }
            });
        } finally {
            // 关闭EventLoopGroup,释放掉所有资源,包括创建的线程
            group.shutdownGracefully();
        }
    }
}

package http;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.ssl.SslContext;

public class ServerHandlerInit extends ChannelInitializer<SocketChannel> {
    
    private final SslContext sslContext;
    
    public ServerHandlerInit(SslContext sslContext) {
        this.sslContext = sslContext;
    }
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (sslContext != null) {
            pipeline.addLast(sslContext.newHandler(ch.alloc()));
        }
        
        // pipeline中的handler可以自定义名称方便排查问题
        // 把应答报文 编码
        pipeline.addLast("encoder", new HttpResponseEncoder());
        // 把请求报文 解码
        pipeline.addLast("decoder", new HttpRequestDecoder());
        
        // 聚合http为一个完整的报文
        pipeline.addLast("aggregator",
                new HttpObjectAggregator(10*1024*1024));
        // 把应答报文压缩
        pipeline.addLast("compressor", new HttpContentCompressor());
        pipeline.addLast(new BusinessHandler());
    }
}

2.业务处理类

package http;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

public class BusinessHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String result = "";
        FullHttpRequest httpRequest = (FullHttpRequest)msg;
        System.out.println(httpRequest.headers());
        try {
            // 获取路径
            String path = httpRequest.uri();
            // 获取body
            String body = httpRequest.content().toString(CharsetUtil.UTF_8);
            // 获取请求方法
            HttpMethod method = httpRequest.method();
            System.out.println("接收到 " + method + "请求");
            // 如果不是这个路径,就直接返回错误
            if (!"/test".equalsIgnoreCase(path)) {
                result = "非法请求!" + path;
                send(ctx,result, HttpResponseStatus.BAD_REQUEST);
                return;
            }
            
            // 如果是GET请求
            if (HttpMethod.GET.equals(method)) {
                // 接收到的消息,做业务处理...
                System.out.println("body :" + body);
                result = "GET请求,应答:" + RespConstant.getNews();
                send(ctx, result, HttpResponseStatus.OK);
                return ;
            }
            
            // 如果是其他类型请求,如post
            if (HttpMethod.POST.equals(method)) {
                // 接收到的消息,做业务逻辑处理
               // ....
                // return;
            }
        } catch (Exception e) {
            System.out.println("处理请求失败!");
            e.printStackTrace();
        } finally {
            // 释放请求
            httpRequest.release();
        }
    }

    private void send(ChannelHandlerContext ctx, String context,
                      HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
    
    // 建立连接时,返回消息
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连接的客户端地址 :" + ctx.channel().remoteAddress());
//        super.channelActive(ctx);
    }
}

3.Client

package http;

import constant.Constant;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;

public class HttpClient {

    public static final String HOST  = "127.0.0.1";
    public static void main(String[] args) throws InterruptedException {
        if (HttpServer.SSL) {
            System.out.println("服务器处于SSL模式,客户端不支持,推出");
            return ;
        }
        
        HttpClient client = new HttpClient();
        client.connect(Constant.DEFAULT_SERVER_IP, Constant.DEFAULT_PORT);
    }
    
    public void connect(String host, int port) throws InterruptedException {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            
                            ch.pipeline().addLast(new HttpClientCodec());
                            // 聚合Http为一个完整的报文
                            ch.pipeline().addLast("aggregator",
                                    new HttpObjectAggregator(10 * 1024 * 1024));
                            // 解压缩
                            ch.pipeline().addLast("decompressor", new HttpContentDecompressor());
                            
                            ch.pipeline().addLast(new HttpClientInboundHandler());
                                    
                        }
                    });
            
            // start 
            ChannelFuture f = b.connect(host, port).sync();
            f.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    System.out.println("连接成功....");
                }
            });

            ChannelFuture closeFuture = f.channel().closeFuture().sync();
            closeFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    System.out.println("关闭成功...");
                }
            });
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
    
    
    
}

package http;

import constant.Constant;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

import java.net.URI;

public class HttpClientInboundHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        FullHttpResponse httpResponse = (FullHttpResponse) msg;
        System.out.println(httpResponse.status());
        System.out.println(httpResponse.headers());
        ByteBuf buf = httpResponse.content();
        System.out.println(buf.toString(CharsetUtil.UTF_8));
        httpResponse.release();
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelActive.......");
        URI uri = new URI("/test");
        String msg = "Hello";
        DefaultFullHttpRequest request =
                new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
                        HttpMethod.GET,
                        uri.toASCIIString(),
                        Unpooled.wrappedBuffer(msg.getBytes("UTF-8")));
        // 构建http请求
        request.headers().set(HttpHeaderNames.HOST, Constant.DEFAULT_SERVER_IP);
        request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        request.headers().set(HttpHeaderNames.CONTENT_LENGTH, 
                request.content().readableBytes());
        // 发送http请求
        ctx.writeAndFlush(request);
                
//        super.channelActive(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
//        super.exceptionCaught(ctx, cause);
    }
}

package http;

import java.util.Random;

public class RespConstant {
    private static final String[] NEWS = {
            "她那时候还太年轻,不知道所有命运赠送的礼物,早已在暗中标好了价格。——斯蒂芬·茨威格《断头皇后》",
            "这是一个最好的时代,也是一个最坏的时代;这是一个智慧的年代,这是一个愚蠢的年代;\n" +
                    "这是一个信任的时期,这是一个怀疑的时期;这是一个光明的季节,这是一个黑暗的季节;\n" +
                    "这是希望之春,这是失望之冬;人们面前应有尽有,人们面前一无所有;\n" +
                    "人们正踏上天堂之路,人们正走向地狱之门。 —— 狄更斯《双城记》",
    };
    
    private static final Random R = new Random();
    
    public static String getNews() {
        return NEWS[R.nextInt(NEWS.length)];
    }
}

package constant;

import java.util.Date;

/**
 * 常量
 */
public class Constant {
    
    public static final Integer DEFAULT_PORT = 7777;
    
    public static final String DEFAULT_SERVER_IP= "127.0.0.1";
    
    // 根据输入信息拼接出一个应答信息
    public static String response(String msg) {
        return "Hello, " + msg + ", Now is" + new Date(System.currentTimeMillis()).toString(); 
    }
}

4.总结分析

如果你想实现http请求,需要把HttpServer中的SSL置为false,结果如下
在这里插入图片描述
在这里插入图片描述
如果你想实现Https的请求,则将SSL的变量置为true,目前的代码中是没有支持客户端的SSL请求的,我们可以在postman或者chrome浏览器中查看
https://localhost:7777/test
在这里插入图片描述
由于我们的证书是自己设置的,所以chrome浏览器认为这个证书不是有效的,需要我们手动点击
在这里插入图片描述

IDEA中会出现红色证书错误,暂时可以不用管,你还可以在postman的GET请求中添加body
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
到此http的简易服务器就搭建好了,如果你的程序出现了以下错误,请检查开头的pom配置是否下载成功,这是由于证书有问题才报的错
在这里插入图片描述

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

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

相关文章

C++虚函数表的简单理解

背景 C的三大特性&#xff1a;封装&#xff0c;继承&#xff0c;多态。其中基于多态特性的虚函数表概念是C开发者面试的长考题。今天梳理一下虚函数表的基本概念。 概念理解 为了实现 C 的多态&#xff0c;C 使用了一种动态绑定的技术。这个技术的核心是虚函数表。 虚表就是为…

小程序直播项目搭建

项目功能&#xff1a; 登录实时聊天点赞功能刷礼物取消关注用户卡片直播带货优惠券直播功能 项目启动&#xff1a; 1 小程序项目创建与配置&#xff1a; 第一步 需要登录小程序公众平台的设置页面进行配置&#xff1a; 首先需要是企业注册的才可以个人不能开通直播功能。服务类…

科技发展趋势,墨水屏电子桌牌将发挥更重要作用

随着科技的不断发展&#xff0c;电子桌牌作为信息展示和宣传的新型设备&#xff0c;逐渐在各个行业得到广泛应用。在国企单位、政府部门、大企业、外企等&#xff0c;墨水屏电子桌牌作为一种新型的数字化展示工具&#xff0c;也已经得到了越来越多的应用。下面&#xff0c;中科…

Spring Boot 初始(快速搭建 Spring Boot 应用环境)

提示&#xff1a; ① 通过下面的简介可以快速的搭建一个可以运行的 Spring Boot 应用&#xff08;估计也就2分钟吧&#xff09;&#xff0c;可以简单的了解运行的过程。 ② 建议还是有一点 spring 和 springMVC的基础&#xff08;其实搭建一个 Spring Boot 环境不需要也没有关系…

微信小程序(十一)表单组件(进阶)

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a;&#xff08;涉及内容较多&#xff0c;建议细看源码&#xff09; 1.radio-group的使用与数据处理 2.checkbox-group的使用与数据处理 3.picker的使用与数据同步处理(此处示范了地域与日期) 源码&#xff1a; form…

数据结构与算法——队列原理及C语言底层实现

数据结构与算法——队列原理及C语言底层实现 队列概念顺序队列1. 顺序队列原理2. 队列的创建3. 入队与出队4. 判断满队与空队5. 清空队列与释放空间6. 主流程测试 链式队列1. 链式队列的创建2. 链式队列入队3. 链式队列出队4. 判断是否为空队5. 清空队列与释放空间6. 主流程测试…

后端开发_单元测试

后端开发_单元测试 1. 简介2. JUnit 4使用方法2.1 jar包引入2.2 测试用例1. 简介 2. JUnit 4使用方法 2.1 jar包引入 1. 本地依赖引入方式 Junit4.jar包 2. maven方式引入jar <dep

基于SSM的影视创作论坛(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的影视创作论坛&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

Sentinel 新版本发布,提升配置灵活性以及可观测配套

作者&#xff1a;屿山 基本介绍 Sentinel 是阿里巴巴集团开源的&#xff0c;面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;承接了阿里巴巴近 15 年的双十一大促流量的核心场景&#xff0c;例如秒杀、冷启动、消息削峰填谷、集群流量控制、实时熔断下游不可用服…

docker安装Rabbitmq教程(详细图文)

目录 1.下载Rabbitmq的镜像 2.创建并运行rabbitmq容器 3.启动web客户端 4.访问rabbitmq的微博客户端 5.遇到的问题 问题描述&#xff1a;在rabbitmq的web客户端发现界面会弹出如下提示框Stats in management UI are disabled on this node 解决方法 &#xff08;1&#…

线程的同步和互斥学习笔记

目录 互斥锁的概念和使用 线程通信-互斥 互斥锁的创建和销毁 申请锁-pthread_mutex_lock 释放锁-pthread_mutex_unlock 读写锁的概念和使用 死锁的避免 互斥锁的概念和使用 线程通信-互斥 临界资源 一次只允许一个任务&#xff08;进程、线程&#xff09;访问的共享资…

Kubeadm安装单master多node节点K8S集群

kubeadm安装k8s1.25版本集群步骤 环境说明实验环境规划集群搭建规划 初始化安装k8s集群的实验环境安装虚拟机更新yum源和操作系统配置机器主机名配置主机hosts文件&#xff0c;相互之间通过主机名互相访问配置主机之间无密码登录关闭交换分区swap&#xff0c;提升性能修改机器内…

漏洞补丁修复之openssl版本从1.1.1q升级到1.1.1t以及python版本默认2.7.5升级到2.7.18新版本和Nginx版本升级到1.24.0

​ 一、Openssl升级 1、查看Openssl安装的版本 openssl version 2、查看Openssl路径 which openssl 3、上传openssl安装包到服务器:openssl-1.1.1t.tar.gz,并且解压,安装: mv /usr/local/openssl /usr/local/backup_openssl_1.1.1q_20240120 mkdir /usr/local/openssl tar…

LeetCode.2765. 最长交替子数组

题目 2765. 最长交替子数组 分析 为了得到数组 nums 中的最长交替子数组的长度&#xff0c;需要分别计算以每个下标结尾的最长交替子数组的长度。为了方便处理&#xff0c;计算过程中需要考虑长度等于 1 的最长交替子数组&#xff0c;再返回结果时判断最长交替子数组的长度…

【AI视野·今日Robot 机器人论文速览 第七十五期】Thu, 11 Jan 2024

AI视野今日CS.Robotics 机器人学论文速览 Thu, 11 Jan 2024 Totally 16 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Analytical Model and Experimental Testing of the SoftFoot: an Adaptive Robot Foot for Walking over Obstacles and Irre…

MMagic调试(训练)dreambooth

时间&#xff1a;2024.1.23 1.dreambooth配置文件 dreambooth在mmagic中的路径&#xff1a; configs/dreambooth本文以dreambooth.py 为例 configs/dreambooth/dreambooth.py2.下载数据集 下载数据集并保存至data/dreambooth/&#xff0c;数据集&#xff1a; https://dri…

[BUUCTF]-PWN:babyfengshui_33c3_2016解析

又是一道堆题&#xff0c;先看保护 关键信息是32位&#xff0c;没开pie 直接看ida 大致是alloc创建堆块&#xff0c;free释放堆块&#xff0c;show查看堆块内容&#xff0c;fill填充堆块内容 其他的都没啥关键的要讲&#xff0c;但alloc那里非常需要解析一下 解释如上图 再具…

npm install运行报错npm ERR! gyp ERR! not ok问题解决

执行npm install的时候报错&#xff1a; npm ERR! path D:..\node_modules\\**node-sass** npm ERR! command failed ...npm ERR! gyp ERR! node -v v20.11.0 npm ERR! gyp ERR! node-gyp -v v3.8.0 npm ERR! gyp ERR! not ok根据报错信息&#xff0c;看出时node-sass运行出现…

基于taro搭建小程序多项目框架

前言 为什么需要这样一个框架&#xff0c;以及这个框架带来的好处是什么&#xff1f; 从字面意思上理解&#xff1a;该框架可以用来同时管理多个小程序&#xff0c;并且可以抽离公用组件或业务逻辑供各个小程序使用。当你工作中面临这种同时维护多个小程序的业务场景时&#xf…

前端面试题-(浏览器内核,CSS选择器优先级,盒子模型,CSS硬件加速,CSS扩展)

前端面试题-(浏览器内核&#xff0c;CSS选择器优先级&#xff0c;盒子模型&#xff0c;CSS硬件加速&#xff0c;CSS扩展&#xff09; 常见的浏览器内核CSS选择器优先级盒子模型CSS硬件加速CSS扩展 常见的浏览器内核 内核描述Trident(IE内核)主要用在window系统中的IE浏览器中&…