`
liuzhengqiu0127
  • 浏览: 7238 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Netty应用高级篇一

阅读更多

本篇主要介绍Netty基于HTTP协议的开发应用

 

一,概念介绍

 

HTTP协议是建立在TCP传输协议之上的应用层的面向对象的协议。

主要特点如下:

1,支持Client/Server模式;

2,简单。客户端向服务端请求服务时,只需指定服务URL,携带必要的请求参数或者消息体;

3,灵活。HTTP允许传输任意类型的数据对象,传输的内容类型由HTTP消息头中的Content-Type加以标记;

4,无状态。指协议对于事务处理没有记忆能力。

 

HTTP URL的格式如下:

http://host[":"port][abs_path]

http表示要通过HTTP协议来定位网络资源;host表示合法的Internet主机域名或者IP地址;port指定一个端口号,为空则使用默认端口80;abs_path指定请求资源的URI。

 

HTTP请求消息HttpRequest:

HTTP请求由三部分组成请求行,消息头,请求正文。

请求行格式:Method Request-URI HTTP-Version CRLF

Method表示请求方法,Request-URI表示统一资源标识符,HTTP-Version表示请求的HTTP协议版本,CRLF表示回车和换行。

请求方法:


 

消息头:



 

 

HTTP响应消息HttpResponse

处理完HTTP客户端的请求之后,HTTP服务端返回响应消息给客户端,HTTP响应也是由三个部分组成,分别是:状态行,消息报头,响应正文。

状态行格式:HTTP-Version Status-Code Reason-Phrase CRLF

Status-Code表示服务器返回的响应状态代码,Reason-Phrase表示服务器返回的错误信息。

状态代码由三个数字组成,有如下5中可能取值。

(1)1xx:指示信息。表示请求已接收,继续处理。

(2)2xx:成功。表示请求已被成功接收,理解,接受。

(3)3xx:重定向。要完成请求必须进行更进一步的操作。

(4)4xx:客户端错误。请求有语法错误或请求无法实现。

(5)5xx:服务端错误。服务器未能处理请求。

常用的响应报头:

Location:响应报头域用于重定向接收者到一个新的位置,Location响应报头域常用语更换域名的时候。

Server:响应报头域包含了服务器用来处理请求的软件信息,与User-Agent请求报头域是相对应的。

WWW-Authenticate:必须被包含在401未授权响应消息中,客户端收到401响应消息,并发送Authorization报头域请求服务器对其进行验证时,服务端响应报头就包含该报头域。

 

二,实例代码

 

由于Netty天生是异步事件驱动的框架,因此基于NIO TCP协议栈开发的HTTP协议栈也是异步非阻塞的。

Netty的HTTP协议栈无论在性能上还是可靠性上,都表现优异,非常适合在非Web容器的场景下应用。

实例场景如下:

文件服务器使用HTTP协议对外提供服务,当客户端通过浏览器访问文件服务器时,对访问路径进行检查,检查失败时返回HTTP 403错误,该页无法访问;如果校验通过,以链接的方式打开当前文件目录,每个目录或者文件都是个超链接,可以递归访问。

HTTP服务端开发:

 

package com.huawei.netty.http;

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;

/**
 * Created by liuzhengqiu on 2017/11/4.
 */
public class HttpFileServer
{
    private static final String DEAFAULT_URL = "/src/main/java/com/huawei/netty";

    public void run(final int port,final String url) throws Exception
    {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try
        {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception
                        {
                            socketChannel.pipeline()
                                    .addLast("http-decoder",new HttpRequestDecoder())
                                    .addLast("http-aggregator",new HttpObjectAggregator(65536))
                                    .addLast("http-encoder",new HttpResponseEncoder())
                                    .addLast("http-chunked",new ChunkedWriteHandler())
                                    .addLast("fileServerHanlder",new HttpFileServerHandler(url));
                        }
                    });
            ChannelFuture future = bootstrap.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 {
        new HttpFileServer().run(8080,DEAFAULT_URL);
    }
}

 

向ChannelPipeline中添加了HTTP请求消息的解码器,随后又添加了HttpObjectAggregator解码器,它的作用是将多个消息转换为单一的FullHttpRequest或者FullHttpResponse,原因是HTTP解码器在每个HTTP消息中会生成多个消息对象。

ChunkedHandler的主要作用是支持异步发送大的码流,但不占用过多的内存,防止发生Java内存溢出错误。

 

package com.huawei.netty.http;

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 javax.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;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.LOCATION;
import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
import static io.netty.handler.codec.http.HttpResponseStatus.*;

/**
 * Created by liuzhengqiu on 2017/11/4.
 */
public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest>
{
    private final String url;

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

    private String sanitizeUri(String uri)
    {
        try
        {
            uri = URLDecoder.decode(uri,"UTF-8");
        }
        catch (UnsupportedEncodingException e)
        {
            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;
    }

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

    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(CONTENT_TYPE,"text/plain;charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception
    {
        if (!fullHttpRequest.getDecoderResult().isSuccess())
        {
            sendError(channelHandlerContext,BAD_REQUEST);
            return ;
        }
        if (fullHttpRequest.getMethod() != HttpMethod.GET)
        {
            sendError(channelHandlerContext,METHOD_NOT_ALLOWED);
            return ;
        }
        final String uri = fullHttpRequest.getUri();
        final String path = sanitizeUri(uri);
        if (path == null)
        {
            sendError(channelHandlerContext,FORBIDDEN);
            return ;
        }

        File file = new File(path);
        if (file.isHidden() || !file.exists())
        {
            sendError(channelHandlerContext,NOT_FOUND);
            return;
        }
        if (file.isDirectory())
        {
            if(uri.endsWith("/"))
            {
                sendListing(channelHandlerContext,file);
            }
            else
            {
                sendRedirect(channelHandlerContext,uri + '/');
            }
            return;
        }
        if (!file.isFile())
        {
            sendError(channelHandlerContext,FORBIDDEN);
            return;
        }

        RandomAccessFile randomAccessFile = null;
        try
        {
            randomAccessFile = new RandomAccessFile(file,"r");
        }catch (FileNotFoundException fnfe)
        {
            sendError(channelHandlerContext,NOT_FOUND);
            return;
        }
        long fileLength = randomAccessFile.length();
        HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,OK);
        setContentLength(response,fileLength);
        setContentTypeHeader(response,file);
        if (isKeepAlive(fullHttpRequest))
        {
            response.headers().set(CONNECTION,HttpHeaders.Values.KEEP_ALIVE);
        }
        channelHandlerContext.write(response);
        ChannelFuture sendFileFuture;
        sendFileFuture = channelHandlerContext.write(new ChunkedFile(randomAccessFile,0,fileLength,8192),
                channelHandlerContext.newProgressivePromise());
        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
            @Override
            public void operationProgressed(ChannelProgressiveFuture channelProgressiveFuture, long progress, long total) throws Exception
            {
                if (total < 0)
                {
                    System.err.println("Transfer progress: "+progress);
                }
                else
                {
                    System.err.println("Transfer progress:"+progress+"/"+total);
                }
            }

            @Override
            public void operationComplete(ChannelProgressiveFuture channelProgressiveFuture) throws Exception
            {
                System.out.println("Transfer complete.");
            }
        });
        ChannelFuture lastContentFuture = channelHandlerContext.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        if (!isKeepAlive(fullHttpRequest))
        {
            lastContentFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }

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

    private static final Pattern ALLOW_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,OK);
        response.headers().set(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 file : dir.listFiles())
        {
            if (file.isHidden() || !file.canRead())
            {
                continue;
            }
            String name = file.getName();
            if (!ALLOW_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,FOUND);
        response.headers().set(LOCATION,newUri);
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}

 

启动HTTP文件服务器,运行结果如下:


三,总结

 

主要介绍了Netty HTTP协议栈的入门级应用。目前最流行的是HTTP+XML开发或者Restful+JSON的形式。后续将进一步介绍

 

 

  • 大小: 93.6 KB
  • 大小: 202.8 KB
  • 大小: 13.8 KB
分享到:
评论

相关推荐

    Netty 框架学习 —— 第一个 Netty 应用(csdn)————程序.pdf

    在本篇关于“Netty框架学习——第一个Netty应用”的文章中,我们将深入理解如何使用Netty构建一个简单的Echo服务器和客户端。Netty是一个高性能、异步事件驱动的网络应用程序框架,广泛应用于Java领域的服务器开发。...

    Netty权威指南 PDF电子书下载 带目录书签 完整版

    基础篇 走进Java NIO 入门篇 Netty NIO开发指南 中级篇 Netty编解码开发指南 高级篇 Netty多协议开发和应用 源码分析篇 Netty功能介绍和源码分析 架构和行业应用篇 Netty高级特性

    深入浅出Netty

    《深入浅出Netty》是一本专注于介绍Netty框架的专著,旨在帮助读者全面理解并...通过深入阅读《深入浅出Netty》这本书,读者可以逐步掌握这些核心概念,并将Netty应用到实际的项目开发中,提升网络通信的效率和可靠性。

    Netty最容易懂的实例和教材

    Netty,作为一款高性能、异步事件驱动的网络应用程序框架,广泛应用于开发高效的服务器和客户端。这个名为"Netty最容易懂的实例和教材"的资源集合,提供了丰富的学习材料,帮助初学者和进阶者更好地理解和掌握Netty...

    使用jboss netty 创建高性能webservice客户端及服务端

    JBoss Netty是一个高性能、异步事件驱动的网络应用程序框架,它为快速开发可维护的高性能协议服务器和客户端提供了丰富的API。本篇文章将深入探讨如何利用JBoss Netty创建高效的Web Service客户端和服务端。 首先,...

    Netty权威指南 PDF电子书 带目录书签 完整版

    本篇文章将深入探讨Netty的关键知识点,帮助读者理解和掌握这一强大的网络编程工具。 1. **Netty概述** - Netty的起源与背景:Netty由JBoss创始人Markus Stenberg发起,旨在提供一个高效、灵活且易于使用的网络...

    基于Netty实现CometStreaming方式的聊天室

    Netty是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。本篇将深入探讨如何利用Netty实现CometStreaming方式的聊天室。 首先,我们要理解CometStreaming是什么。...

    Netty权威指南

    Netty 基础篇:java的IO演进之路; BIO ;NIO;伪异步;NIO类库 ; 入门篇:Jetty简单应用入门;...高级篇:Http协议开发; Netty 协议栈开发(数据结构定义,消息编解码,握手安全认证,心跳检测等); WebSocket等

    Java学习之IO总结及mina和netty

    这篇博客“Java学习之IO总结及mina和netty”探讨了Java IO的基础知识,并深入到两个高级网络通信框架——Mina和Netty。Mina和Netty都是基于NIO(非阻塞IO)的高性能网络应用框架,它们简化了复杂网络编程的实现。 *...

    NIO和Netty框架的学习

    本篇文章将深入探讨NIO的基本原理和Netty框架的使用,帮助你从初学者逐渐晋升为高级开发者。 首先,我们来了解**NIO**。NIO是一种与传统的IO模型不同的I/O方式,传统IO基于缓冲区的读写,而NIO则是基于通道...

    Netty开发记录三

    在本篇“Netty开发记录三”中,我们将深入探讨Netty框架的使用,这是一个高效、灵活且可扩展的网络应用程序框架,广泛应用于高性能服务器和客户端的开发。Netty简化了网络编程,提供了异步事件驱动的网络通信模型,...

    javaNetty仿微信源码

    在Java领域,Netty是一个高性能、异步事件驱动的网络应用程序框架,常用于构建可伸缩的、高并发的服务器。本篇将深入探讨如何利用Java Netty框架来实现类似微信的功能,以及相关的技术细节。 1. **Netty基础** - *...

    java高级编程胶片

    虽然描述部分为空,但根据提供的博客链接(https://xgw123485.iteye.com/blog/1178464),我们可以推测这可能是一个在线分享的资源,博主xgw123485在ITEYE平台上发布了一篇关于Java高级编程的文章或者教程。...

    java开发程序指南进阶篇

    同时,NIO(非阻塞I/O)和异步I/O(AIO)为高性能网络应用提供了可能,如Netty框架就是基于这些概念构建的。 异常处理是Java程序健壮性的保障。通过try-catch-finally语句块,我们可以捕获并处理运行时出现的异常,...

    jboss-3.2.5.zip

    本篇文章将详细讲解"jboss-3.2.5.zip"这一版本的JBoss安装、配置及EJBCA的安装和设置过程。 一、JBoss 3.2.5简介 JBoss 3.2.5是在2004年发布的一个版本,它是Java社区的重要里程碑,为开发者提供了轻量级、开源的...

    Java高手真经(网络开发卷)源码

    - Netty框架:高性能的异步事件驱动网络应用框架,可能用于构建高效的网络应用服务器。 6. **性能优化与调试** - 网络延迟优化:源码可能包含如何减少网络延迟,提高网络通信效率的策略。 - 错误处理与日志记录...

    java高手真经:网络开发卷源码

    7. **Java网络框架**:Spring Boot、Netty、Grizzly等框架简化了网络应用的开发,提供了高级功能如异步处理、性能优化和自定义协议支持。 8. **分布式服务**:Java在分布式计算中的应用,如RMI(Remote Method ...

    2021一线大厂java面试题.zip

    三、高级篇 1. JVM优化:了解JVM内存结构,垃圾回收机制,调优策略。 2. Spring框架:理解IoC、AOP原理,使用Spring Boot和Spring Cloud进行微服务开发。 3. 数据库:MySQL基础,索引原理,事务处理,JDBC操作,...

Global site tag (gtag.js) - Google Analytics