netty权威指南~第10章——HTTP开发

10.2.1 HTTP 服务端例程场景描述

我 们 以 文 件 服 务 器 为 例 学 习 Netty 的 HTTP 服 务 端 入 门 开 发 , 例 程 场 景 如 下 : 文 件 服务 器 使 用 HTTP 协 议 对 外 提 供 服 务 , 当 客 户 端 通 过 浏 览 器 访 问 文 件 服 务 器 时 , 对 访 问 路 径行 检 查 , 检 查 失 败 时 返 回 HTTP 403 错 误 , 该 页 无 法 访 问 ; 如 果 校 验 通 过 , 以 链 接 的 方弋 打 开 当 前 文 件 目 录 , 每 个 目 录 或 者 文 件 都 是 个 超 链 接 , 可 以 递 归 访 问 。

10.2.2 HTTP服务器端开发

本文主要是写一个入门程序让大家了解netty的强大。本文主要编写一个基于http协议的文件服务器,可以使用浏览器来访问服务器的目录,下载文件等。如果不熟悉http协议可以先了解http相应相关的内容。
HttpFileServer.java

package com.viagra.chapter10.http;

import io.netty.bootstrap.ServerBootstrap;
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.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;

/**
 * @Auther: viagra
 * @Date: 2019/8/2 10:25
 * @Description:
 */
public class HttpFileServer {

    private final int port=80;
    private final String localDir="d:/";

    public void run() throws  Exception{
        EventLoopGroup acceptorGroup = new NioEventLoopGroup();
        EventLoopGroup clientGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBookstrap = new ServerBootstrap();
            serverBookstrap.group(acceptorGroup, clientGroup).channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .childHandler(new ChannelInitializer<SocketChannel>(){
                        protected void initChannel(SocketChannel sc) throws Exception {
                            sc.pipeline().addLast("http-decoder",new HttpRequestDecoder());
                            sc.pipeline().addLast("http-aggregator",new HttpObjectAggregator(64*1024));
                            sc.pipeline().addLast("http-encoder",new HttpResponseEncoder());
                            sc.pipeline().addLast("http-handler",new HttpFileServerHandler(localDir));
                        }
                    });
            ChannelFuture channelFuture = serverBookstrap.bind(port).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            acceptorGroup.shutdownGracefully();
            clientGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) throws Exception {
        new HttpFileServer().run();
    }



}

Netty提类似linux管道机制的使用方法,可以向pipeline添加多个ChannelHandler,Netty会依次调用添加的handler来处理,这个很像java web中的servlet和filter的概念。

上述代码的调用顺序是:

1.调用HttpRequestDecoder将ByteBuf转成HttpRequest和HttpContent对象。即将字节流缓存对象解码成pojo对象

2.调用HttpObjectAggregator将HttpRequest和它跟着的HttpContent对象聚合成一个对象FullHttpRequest

3.调用HttpRequestHandler来处理真正的业务逻辑,在HttpRequestHandler里面就可以获取到FullHttpRequest对象。

4.调用HttpResponseEncoder将HttpRequestHandler写出的FullHttpResponse对象编码成字节流。

我们在声明ServerBootstrap服务器引导对象的时候传入了两个EventLoopGroup对象这两个对象有什么作用呢?

在这里插入图片描述
groupA为acceptorGroup组,groupB为clientGroup组。groupA的作用是接受连接过来的客户端,并把他们交给groupB,groupB负责具体的请求处理。如果一个group既负责接受连接也负责处理连接后的会话,那么在请求很大的时候一个group会成为瓶颈,会导致一些连接的超时,如果将两个功能分离开来,就不会有这个问题。

HttpFileServerHandler.java

package com.viagra.chapter10.http;

import java.io.File;
import java.net.URLDecoder;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import javax.activation.MimetypesFileTypeMap;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;

/**
 * @Auther: viagra
 * @Date: 2019/8/2 10:38
 * @Description:
 */
public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest>{
    private static final String CRLF = "\r\n";
    private   String localDir;

    private static SimpleDateFormat sdf=new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");


    public HttpFileServerHandler(String localDir){
        this.localDir=localDir;
    }
    public static final HttpVersion HTTP_1_1 = new HttpVersion("HTTP", 1, 1, true);
    @Override
    public void messageReceived(ChannelHandlerContext ctx, FullHttpRequest req)throws Exception {
        //解码不成功
        if(!req.decoderResult().isSuccess())
        {
            sendErrorToClient(ctx,HttpResponseStatus.BAD_REQUEST);
            return ;
        }
        if(req.method().compareTo(HttpMethod.GET)!=0){
            sendErrorToClient(ctx,HttpResponseStatus.METHOD_NOT_ALLOWED);
            return;
        }
        String uri=req.uri();
        uri=URLDecoder.decode(uri, "utf-8");
        String filePath=getFilePath(uri);
        File file=new File(filePath);
        //如果文件不存在
        if(!file.exists()){
            sendErrorToClient(ctx,HttpResponseStatus.NOT_FOUND);
            return;
        }
        //如果是目录,则显示子目录
        if(file.isDirectory()){
            sendDirListToClient(ctx,file,uri);
            return;
        }
        //如果是文件,则将文件流写到客户端
        if(file.isFile()){
            sendFileToClient(ctx,file,uri);
            return;
        }
        ctx.close();
    }

    public String getFilePath(String uri) throws Exception{
        return localDir+uri;
    }

    private void sendErrorToClient(ChannelHandlerContext ctx,HttpResponseStatus status) throws Exception{
        ByteBuf buffer=Unpooled.copiedBuffer(("系统服务出错:"+status.toString()+CRLF).getBytes("utf-8"));
        FullHttpResponse resp=new DefaultFullHttpResponse(HTTP_1_1,status,buffer);
        resp.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=utf-8");
        ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
    }
    private void sendDirListToClient(ChannelHandlerContext ctx, File dir,String uri) throws Exception {
        StringBuffer sb=new StringBuffer("");
        String dirpath=dir.getPath();
        sb.append("<!DOCTYPE HTML>"+CRLF);
        sb.append("<html><head><title>");
        sb.append(dirpath);
        sb.append("目录:");
        sb.append("</title></head><body>"+CRLF);
        sb.append("<h3>");
        sb.append("当前目录:"+dirpath);
        sb.append("</h3>");
        sb.append("<table>");
        sb.append("<tr><td colspan='3'>上一级:<a href=\"../\">..</a>  </td></tr>");
        if(uri.equals("/")){
            uri="";
        }else
        {
            if(uri.charAt(0)=='/'){
                uri=uri.substring(0);
            }
            uri+="/";
        }

        String fnameShow;
        for (File f:dir.listFiles()) {
            if(f.isHidden()||!f.canRead()){
                continue;
            }
            String fname=f.getName();
            Calendar cal=Calendar.getInstance();
            cal.setTimeInMillis(f.lastModified());
            String lastModified=sdf.format(cal.getTime());
            sb.append("<tr>");
            if(f.isFile()){
                fnameShow="<font color='green'>"+fname+"</font>";
            }else
            {
                fnameShow="<font color='red'>"+fname+"</font>";
            }
            sb.append("<td style='width:200px'> "+lastModified+"</td><td style='width:100px'>"+Files.size(f.toPath())+"</td><td><a href=\""+uri+fname+"\">"+fnameShow+"</a></td>");
            sb.append("</tr>");

        }
        sb.append("</table>");
        ByteBuf buffer=Unpooled.copiedBuffer(sb.toString(),CharsetUtil.UTF_8);
        FullHttpResponse resp=new DefaultFullHttpResponse(HTTP_1_1,HttpResponseStatus.OK,buffer);
        resp.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=utf-8");
        ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
    }

    private void sendFileToClient(ChannelHandlerContext ctx, File file, String uri) throws Exception {
        ByteBuf buffer=Unpooled.copiedBuffer(Files.readAllBytes(file.toPath()));
        FullHttpResponse resp=new DefaultFullHttpResponse(HTTP_1_1,HttpResponseStatus.OK,buffer);
        MimetypesFileTypeMap mimeTypeMap=new MimetypesFileTypeMap();
        resp.headers().set(HttpHeaderNames.CONTENT_TYPE, mimeTypeMap.getContentType(file));
        ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
    }


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

HttpRequestHandler继承SimpleChannelInboundHandler 类,当然也可以继承ChannelHandlerAdapter类,其实SimpleChannelInboundHandler 就是ChannelHandlerAdapter的子类。

对于SimpleChannelInboundHandler有以下需要注意的 :

1.它可以指定只处理某一种类型的消息

2.根据SimpleChannelInboundHandler的构造函数

protected SimpleChannelInboundHandler(boolean autoRelease)

它会自动释放已经处理过的消息。

3.根据实现messageReceived抽象方法。

在messageReceived方法中我们要实现我们具体的业务逻辑:

我们的http文件服务器的需求如下:

1.显示当前的路径下的目录和文件,点击文件就下载该文件,点击目录则切换到改目录下面。

2.提供返回上一级功能。

3.简单的异常处理。

运行结果:

在这里插入图片描述
注意:

sendFileToClient方法中直接将文件的字节流写到FullHttpResponse中,在文件不大的情况下可以这样做,在文件比较大的情况下这样做很容易出现内存溢出之类的问题。需要考虑使用http trunked协议机制来传输大文件。这个后面会继续说。

运行的时候把HttpFileServer.java中localDir改成你自己的目录即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值