使用Netty实现文件传输的HTTP服务器和客户端

news2024/11/18 19:53:00

现在我们来用netty实现文件传输的HTTP服务器和客户端

pom依赖文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>jdk21Test001</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
        <dependency>
            <groupId>jakarta.activation</groupId>
            <artifactId>jakarta.activation-api</artifactId>
            <version>2.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.angus</groupId>
            <artifactId>angus-mail</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

 
</project>

1、现在开始搞服务端代码。

服务端代码:

package com.netty.file;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
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.NioServerSocketChannel;
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.stream.ChunkedWriteHandler;

public class HttpFileServer {

    private static final String DEFAULT_URL = "/src/main/resources/";

    public void run(final int port, final String url) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("http-decoder", new HttpRequestDecoder());
                            ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
                            ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
                            ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
                            ch.pipeline().addLast("fileServerHandler", new HttpFileServerHandler(url));
                        }
                    });

            ChannelFuture future = b.bind("127.0.0.1", port).sync();
            System.out.println("HTTP文件目录服务器启动,网址是 : " + "http://127.0.0.1:" + port + url);
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        String url = DEFAULT_URL;
        new HttpFileServer().run(port, url);
    }
}

服务端处理器代码:

package com.netty.file;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;
import jakarta.activation.MimetypesFileTypeMap;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.regex.Pattern;

public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final String url;

    public HttpFileServerHandler(String url) {
        this.url = url;
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        if (!request.decoderResult().isSuccess()) {
            sendError(ctx, HttpResponseStatus.BAD_REQUEST);
            return;
        }
        if (request.method() != HttpMethod.GET) {
            sendError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);
            return;
        }
        final String uri = request.uri();
        final String path = sanitizeUri(uri);
        if (path == null) {
            sendError(ctx, HttpResponseStatus.FORBIDDEN);
            return;
        }
        File file = new File(path);
        if (file.isHidden() || !file.exists()) {
            sendError(ctx, HttpResponseStatus.NOT_FOUND);
            return;
        }
        if (file.isDirectory()) {
            if (uri.endsWith("/")) {
                sendListing(ctx, file);
            } else {
                sendRedirect(ctx, uri + '/');
            }
            return;
        }
        if (!file.isFile()) {
            sendError(ctx, HttpResponseStatus.FORBIDDEN);
            return;
        }
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(file, "r");
        } catch (FileNotFoundException e) {
            sendError(ctx, HttpResponseStatus.NOT_FOUND);
            return;
        }
        long fileLength = randomAccessFile.length();
        HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        HttpUtil.setContentLength(response, fileLength);
        setContentTypeHeader(response, file);
        if (HttpUtil.isKeepAlive(request)) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        ctx.write(response);
        ChannelFuture sendFileFuture;
        sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());
        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
            @Override
            public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
                if (total < 0) {
                    System.err.println("Transfer progress: " + progress);
                } else {
                    System.err.println("Transfer progress: " + progress + " / " + total);
                }
            }

            @Override
            public void operationComplete(ChannelProgressiveFuture future) {
                System.err.println("Transfer complete.");
            }
        });
        ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        if (!HttpUtil.isKeepAlive(request)) {
            lastContentFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }

    private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");

    private String sanitizeUri(String uri) {
        try {
            uri = URLDecoder.decode(uri, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            try {
                uri = URLDecoder.decode(uri, "ISO-8859-1");
            } catch (UnsupportedEncodingException e1) {
                throw new Error();
            }
        }
        if (!uri.startsWith(url)) {
            return null;
        }
        if (!uri.startsWith("/")) {
            return null;
        }
        uri = uri.replace('/', File.separatorChar);
        if (uri.contains(File.separator + '.') || uri.contains('.' + File.separator) || uri.startsWith(".") || uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()) {
            return null;
        }
        return System.getProperty("user.dir") + File.separator + uri;
    }

    private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");

    private static void sendListing(ChannelHandlerContext ctx, File dir) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
        StringBuilder buf = new StringBuilder();
        String dirPath = dir.getPath();
        buf.append("<!DOCTYPE html>\r\n");
        buf.append("<html><head><title>");
        buf.append(dirPath);
        buf.append(" 目录:");
        buf.append("</title></head><body>\r\n");
        buf.append("<h3>");
        buf.append(dirPath).append(" 目录:");
        buf.append("</h3>\r\n");
        buf.append("<ul>");
        buf.append("<li>链接:<a href=\"../\">..</a></li>\r\n");
        for (File f : dir.listFiles()) {
            if (f.isHidden() || !f.canRead()) {
                continue;
            }
            String name = f.getName();
            if (!ALLOWED_FILE_NAME.matcher(name).matches()) {
                continue;
            }
            buf.append("<li>链接:<a href=\"");
            buf.append(name);
            buf.append("\">");
            buf.append(name);
            buf.append("</a></li>\r\n");
        }
        buf.append("</ul></body></html>\r\n");
        ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8);
        response.content().writeBytes(buffer);
        buffer.release();
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND);
        response.headers().set(HttpHeaderNames.LOCATION, newUri);
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void setContentTypeHeader(HttpResponse response, File file) {
        MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        if (ctx.channel().isActive()) {
            sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

在resource下面建立一个资源文件:index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>hello</title>
</head>
<body>
  <h1>Hello World</h1>
</body>
</html>

这个时候,我们用浏览器就可以直接访问了。

运行项目:

点击这个地址 http://127.0.0.1:8080/src/main/resources/ 这个地址。

点击index.html  可以直接显示出来。

2、现在我们开始搞客户端。

客户端代码:

package com.netty.file;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;

public class HttpFileClient {

    public void connect(String host, int port, final String fileName) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("http-decoder", new HttpResponseDecoder());
                            ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
                            ch.pipeline().addLast("http-encoder", new HttpRequestEncoder());
                            ch.pipeline().addLast("fileClientHandler", new HttpFileClientHandler(fileName));
                        }
                    });

            ChannelFuture future = b.connect(host, port).sync();
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        String host = "127.0.0.1";
        int port = 8080;
        String fileName = "/src/main/resources/index.html";
        new HttpFileClient().connect(host, port, fileName);
    }
}

客户端处理器:

package com.netty.file;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

public class HttpFileClientHandler extends SimpleChannelInboundHandler<HttpObject> {

    private final String fileName;

    public HttpFileClientHandler(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, fileName);
        request.headers().set(HttpHeaderNames.HOST, "127.0.0.1");
        request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
        request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP.toString() + ',' + HttpHeaderValues.DEFLATE.toString());
        ctx.writeAndFlush(request);
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpResponse) {
            HttpResponse response = (HttpResponse) msg;
            System.out.println("STATUS: " + response.status());
            System.out.println("VERSION: " + response.protocolVersion());
            System.out.println();
            if (!response.status().equals(HttpResponseStatus.OK)) {
                ctx.close();
                return;
            }
        }
        if (msg instanceof HttpContent) {
            HttpContent content = (HttpContent) msg;
            ByteBuf buf = content.content();
            System.out.print(buf.toString(CharsetUtil.UTF_8));
            buf.release();
            if (msg instanceof LastHttpContent) {
                ctx.close();
            }
        }
    }

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

在服务端启动的条件下,启动客户端。结果展示:

这里只是将它们输出到控制台而已,如果想要实现下载,只需要在输出的时候调用:

 FileChannel fileChannel = new RandomAccessFile(new File("index.html"), "rw").getChannel();
            buf.readBytes(fileChannel, buf.readableBytes());

这样就行了。

结果展示:

下载成功。

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

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

相关文章

国内好用的CRM系统你知道哪些?看看这篇吧

选择CRM系统需要考虑许多因素&#xff0c;其中一个重要的标准是用户数量。使用广泛、受到用户认可的CRM系统&#xff0c;无论是在功能、易用性、灵活性还是价格上&#xff0c;都会给企业带来更多的益处。有什么国内用得比较多的CRM系统吗&#xff1f; 以Zoho CRM为例&#xff…

Linux学习教程(第十四章 Linux系统服务管理)一

第十四章 Linux系统服务管理&#xff08;一&#xff09; 什么是系统服务&#xff1f;服务是在后台运行的应用程序&#xff0c;并且可以提供一些本地系统或网络的功能。 那么&#xff0c;Linux 中常见的服务有那些&#xff0c;这些服务怎么分类&#xff0c;服务如何启动&#x…

视频处理关键知识

1 引言 视频技术发展到现在已经有100多年的历史&#xff0c;虽然比照相技术历史时间短&#xff0c;但在过去很长一段时间之内都是最重要的媒体。由于互联网在新世纪的崛起&#xff0c;使得传统的媒体技术有了更好的发展平台&#xff0c;应运而生了新的多媒体技术。而多媒体技术…

为什么感染HPV的人越来越多?劲松中西医结合医院专家发表看法

近年来&#xff0c;HPV感染率在我国呈现上升趋势&#xff0c;引起了社会的广泛关注。HPV是一种人乳头瘤病毒&#xff0c;主要通过性接触传播&#xff0c;也是引起宫颈癌的主要原因之一。那么&#xff0c;为什么我国的HPV感染率如此高呢&#xff1f; 首先&#xff0c;我们需要了…

手把手教你写 Compose 动画 -- 讲的不能再细的 AnimationSpec 动画规范

前面我们聊过 animateDpAsState 和 Animatable 两种动画 API 的用法&#xff0c;但只是简单了解了&#xff0c;这两个函数内部都有一个共同的核心参数&#xff1a;AnimationSpec。 Composable fun animateDpAsState(targetValue: Dp,animationSpec: AnimationSpec<Dp> …

友思特 Neuro-R:快速部署实时推理API

科技赋能&#xff0c;视觉技术在缺陷检测方面的应用为生产制造自动化程度带来了重大影响&#xff0c;有效缩短了检测时间&#xff0c;提升了生产效率&#xff0c;创造出更多的经济效益。通过视觉技术的精准分析和快速判断&#xff0c;缺陷检测变得更加准确、高效&#xff0c;无…

Java二十一章 网络通信

1 网络程序设计基础 网络程序设计编写的是与其他计算机进行通信的程序。 局域网与互联网 服务器是指提供信息的计算机或程序&#xff0c;客户机是指请求信息的计算机或程序。网络用于连接服务器与客户机&#xff0c;实现两者间的相互通信。 网络协议 网络协议规定了计算机…

echarts绘制一个柱状折线图

其他echarts&#xff1a; echarts绘制一个环形图 echarts绘制一个饼图 echarts绘制一个环形图2 效果&#xff1a; 代码&#xff1a; <template><div class"wrapper"><!-- 柱状图 --><div v-if"type new_bar" ref"barChar…

2023最新八股文前端面试题

第一章 Css 1.说一下CSS的盒模型。 在HTML页面中的所有元素都可以看成是一个盒子盒子的组成:内容content、内边距padding、边框border、外边距margin盒模型的类型: 标准盒模型 margin border padding content IE盒模型 margin content(border padding) 控制盒模型的模式…

如何选择靠谱的软件测试外包公司?CMA、CNAS软件测试报告获取

作为信息科技产业的代表之一&#xff0c;软件公司受到了越来越多的关注&#xff0c;它们的发展为我国的科技创新提供了强大的战略支撑。软件测试作为提升软件产品质量的后盾&#xff0c;日益成为一个专业化、标准化和规范化的行业&#xff0c;软件测试外包公司就是这种背景下成…

每日汇评:由于非农就业数据可能低迷,黄金恐再次测试2050美元上方

周五早间&#xff0c;金价在2,030美元附近扩大区间&#xff1b; 美元正在企稳&#xff0c;美债收益率坚持复苏收益&#xff1b; 由于美国就业大概率疲软&#xff0c;黄金日线图倾向于看涨&#xff1b; 周五早间&#xff0c;金价连续第四个交易日延续区间波动走势&#xff0c;因…

员工持股平台模式有哪几种?

员工持股平台模式 目前在现有的市场环境下持股平台的模式主要有公司型的持股平台以及有限合伙企业的持股平台。 &#xff08;一&#xff09;公司型员工持股平台 设立公司型的员工持股平台的唯一目的是为了让平台公司受让母公司的股权&#xff0c;从而实现员工间接持有母公司股权…

外贸行业的CRM系统和其它CRM有什么区别?

外贸行业对客户管理的追求日益提高&#xff0c;为了应对客户需求的变化和多元性&#xff0c;外贸企业需要借助CRM管理系统实现智能管理。下面&#xff0c;我们将详细探讨外贸CRM的概念、特点和具体应用。 什么是外贸CRM&#xff1f; 外贸CRM是指针对外贸行业的客户关系管理系…

3-Mybatis

文章目录 Mybatis概述什么是Mybatis&#xff1f;Mybatis导入知识补充数据持久化持久层 第一个Mybatis程序&#xff1a;数据的增删改查查创建环境编写代码1、目录结构2、核心配置文件&#xff1a;resources/mybatis-config.xml3、mybatis工具类&#xff1a;Utils/MybatisUtils4、…

电商行业邮件营销全攻略:解决方案与实施技巧

在电商行业这样多元化且快节奏的行业中&#xff0c;有效、及时的沟通则是其业务发展的基础之一。您需要在客户生命周期的各个阶段与他们进行交互&#xff0c;需要在恰当的时机推送他们需要的信息&#xff0c;更重要的是&#xff0c;您需要根据客户的购买经历迅速制定营销方案&a…

STL(五)(queue篇)

我发现之前一版在电脑上看 常用函数部分 没有问题,由于是手打上去的,在手机上看会发生错位问题,现已将电脑原版 常用函数部分 截图改为图片形式,不会再发生错位问题,非常感谢大家的支持 ### priority_queue优先队列出现频率非常高,尤为重要(是一定要掌握的数据结构) 1.queue队…

vmware虚拟机17 安装macos14过程及问题处理亲测

前期准备 1、可引导可虚拟机安装的macOS Sonoma 14 ISO镜像安装文件 我找到得地址&#xff0c;下载自行解决啦 2、VMware虚拟机应用软件 官网下载就好&#xff0c;搜个码搞定 3、解锁工具macOS Unlocker 开始安装&#xff1a; 1、打开VMware软件&#xff0c;新建一个系统…

[MySQL] MySQL复合查询(多表查询、子查询)

前面我们学习了MySQL简单的单表查询。但是我们发现&#xff0c;在很多情况下单表查询并不能很好的满足我们的查询需求。本篇文章会重点讲解MySQL中的多表查询、子查询和一些复杂查询。希望本篇文章会对你有所帮助。 文章目录 一、基本查询回顾 二、多表查询 2、1 笛卡尔积 2、2…

【Vulnhub 靶场】【Funbox: GaoKao】【简单】【20210606】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/funbox-gaokao,707/ 靶场下载&#xff1a;https://download.vulnhub.com/funbox/FunboxGaoKao.ova 靶场难度&#xff1a;简单 发布日期&#xff1a;2021年06月06日 文件大小&#xff1a;1.3 GB 靶场作者&#…

虚拟化之指令的Trap和仿真

有时,虚拟机监控程序需要在虚拟机(VM)中模拟操作。例如,VM内的软件可能尝试配置与功耗管理或缓存一致性相关的低级处理器控件。通常,您不希望将VM直接访问这些控件,因为它们可能被用于突破隔离,或影响系统中的其他VM。 trap在执行给定操作(例如读取寄存器)时引发异常…