Netty三、HTTP协议

1. HTTP协议

​ HTTP (超文本传输协议)协议是建立在TCP传输协议之上的应用层协议,它的发展 是万维网协会和Internet工作小组IETF合作的结果,是一个属于应用层的面向对象 的协议,适用于分布式超媒体信息系统。

1.1. 特点:

  1. 支持 Client/Server 模式。
  2. 客户向服务器请求服务时,只需指定服务URL,携带必要的请求参数或者消息体。
  3. HTTP允许传输任意类型的数据对象,传输的内容类型由HTTP消息头中的Coment-Type加以标记。
  4. HTTP协议是无状态协议,无状态是指协议对于事务处理没有记忆能 力。缺少状态意味着如果后续处理需要之谕的信息,则它必须重传,这样可能导 致每次连接传送的数据貰増大。另一方面,在服务器不需要先前信息时它的应答 就较快,负载较轻

1.2. URL

​ URL是一种特殊类型的URI,包含了用于査找某个资源的足够的信息

http://host:port /abs_path

  1. http表示要通过HTTP协议来定位网絡资源
  2. host表示合法的Internet主机域 名或者IP地址
  3. port指定一个端口号,为空则使用默认端口 80
  4. abs_path指定请求资源 的URI,如果URL中没有给出abs_path,那么当它作为请求URI时,必须以"/"的形式 给出,通常这点工作浏览器会自动帮我们完成

1.3. HTTP 请求消息(HttpRequest)

1.3.1. 请求行

请求行以一个方法符开头,以空格分开,后面跟着请求的URI和协议的版本,格式为: Method Request-URI HTTP-Version CRLF

其中Method表示请求方法,Request-URI是一个统一资源标识符,HTTP-Version表 示请求的HTTP协议版本,CRLF表示回车和换行(除了作为结尾的CRLF外,不允许出 现单独的CR或LF字符)。

请求方法:

  • GET:请求获取Request-URI所标识的资源;
  • POST:在Request-URI所标识的资源后附加新的提交数据:HEAD:请求获取由Request.URI所标识的资源的响应消息报头;
  • PUT:请求服务器存储一个资源,并用Request-URI作为其标识:
  • DELETE:请求服务器删除Request-URI所标识的资源;
  • TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断;
  • CONNECT:保留将来使用:
  • OPTIONS:请求査询服务器的性能,或者査询与资源相关的选项和需求。

1.3.2. 请求头

部分请求头

名称CKEY)作 用
Accept用于指定客户端接受哪些类型的信息. 例妬Accept:image/gif.表明客户端希望接受G1F图像格式的资源
Accq)t-、Charsel用于指定客户端接受的字符集。 例如:Accept-Charset:iso-8859-1 ,gb2312,如果在请求消息中没有设置这个域,双认是任何字符集都 可以接受
Acccpt-Encoding类似于Accept,但墨它用于指定可接受的内容编玛。 例如:Accept-Encodinggzip.deflate,如果请求消息中没有设置这个域,则服务器假定客户端对各种 内容编码都叮以接受
Accept-Language类似于Accept,但是它用于指定-种自然语言・ 例如:Accept-Language:zh-cn.如果请求消息中没有设置这个报头域,则服务器假定客户端对各种语 言都可以接受
Authorization主要用于证明客户編有权査看某个资源.当浏览器访问一个页面时,如果收到服务器的响应代码为401 (未授权).可以发送-个包含Aulhori/ation 求报头域的请求,要求服务嚣对其进行认证
Host发送请求时,该报头域是必需的.用于折定被请求资源的Interne.主机和端口号,它通常是从HTTP URL中提取出来的
User-Agent允许客户端将它的操作系统、浏览器和M他属性告诉服务器
Content-Length请求消息体的K度
Content-Type表示后面的文档属于什么MIME类型。Servlet默认为tcxt/plain,但通常需要显式地指定为text/htmL 由于经常要设置Content-Type.因此11 ttpServ 1 etResponse提供了一个专用的方法sctContcntTypc
Connection连接类型

1.3.3. 请求正文

HTTP 响应消息(HttpResponse)

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

状态行的格式为:HTTP-Version Status-Code Reason-Phrase CRLF,其中 HTTP-Version 表示服务器HTTP协议的版本,Status-Code表示服务器返回的响应状态代码。

状态代码:

  • Ixx:指示信息。表示请求已接收,继续处理:
  • 2xx:成功。表示请求已被成功接收、理解、接受:
  • 3xx:重定向。要完成请求必须进行更进步的操作:
  • 4xx:客户端错误。请求有语法错误或请求无法实现;
  • 5xx:服务器端错误。服务器未能处理请求。
状态码状态描述
200OK:客户端请求成功
4(M)BadRcquc引:客户端请求有语法铅误.不能被服务腭所理解
401Unauthorized:请求未经授权,这个状态代码必须和WWW-Authcnticatc报头域-起使用
403Forbidden:服务器收到请求,但是拒绝提供服务
404Not Found:请求资源不存在
500Internal Server Error: IJK务器发生不可预期的緒误
503Server Unavailable:服务器当前不能处理客户端的请求.一段时间后可能恢岌止常

2. Netty 实现HTTP服务端,文件服务器

服务端编码跟之前一致,处理链修改下

.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        // 请求消息解码器
        ch.pipeline().addLast("http-decoder", new HttpRequestDecoder());
        // 将多个消息转换为单一的 FuIlHttpRequcst或者FullHttpResponsc,原因是HTTP解码器在每个HTTP消息中会生成 多个消息对象。
        ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
        //响应解码器
        ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
        // 支持异步发送大的码流(例如大的文件传输),但不占 用过多的内存,防止发生Java内存溢出错误。
        ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
        // 业务逻辑
        ch.pipeline().addLast("fileServerHandler", new HttpFileServerHandler(url));
    }
});

HttpFileServerHandler 业务处理器

public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    private final String url;

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

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        // 解码是否成功
        if (!request.getDecoderResult().isSuccess()) {
            sendError(ctx, BAD_REQUEST);
            return;
        }
        
        // 是否为GET方法
        if (request.getMethod() != GET) {
            sendError(ctx, METHOD_NOT_ALLOWED);
            return;
        }
        
        // 获取当前请求的路径
        final String uri = request.getUri();
        final String path = sanitizeUri(uri);
        if (path == null) {
            sendError(ctx, FORBIDDEN);
            return;
        }
        
        // 判断请求文件是否存在
        File file = new File(path);
        if (file.isHidden() || !file.exists()) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        
        // 是否为文件夹
        if (file.isDirectory()) {
            if (uri.endsWith("/")) {
                // 展示文件夹内容
                sendListing(ctx, file);
            } else {
                // 重定向
                sendRedirect(ctx, uri + '/');
            }
            return;
        }
        
        if (!file.isFile()) {
            sendError(ctx, FORBIDDEN);
            return;
        }
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(file, "r");// 以只读的方式打开文件
        } catch (FileNotFoundException fnfe) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        
        
        long fileLength = randomAccessFile.length();
        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
        setContentLength(response, fileLength);
        setContentTypeHeader(response, file);
        
         /**
         * 在早期的HTTP/1.0中,每次http请求都要创建一个连接,而创建连接的过程需要消耗资源和时间,为了减少资源消耗,缩短响应时间,就需要重用连接。
         * 在后来的HTTP/1.0中以及HTTP/1.1中,引入了重用连接的机制,就是在http请求头中加入Connection: keep-alive来告诉对方这个请求响应完成后不要关闭,
         * 下一次咱们还用这个请求继续交流。协议规定HTTP/1.0如果想要保持长连接,需要在请求头中加上Connection: keep-alive,而HTTP/1.1默认是支持长连接的,
         * 有没有这个请求头都行。
         * 当然了,协议是这样规定的,至于支不支持还得看服务器(比如tomcat)和客户端(比如浏览器)的具体实现。
         * 在实践过程中发现谷歌浏览器使用HTTP/1.1协议时请求头中总会带上Connection: 
         * keep-alive,另外通过httpclient使用HTTP/1.0协议去请求tomcat时,即使带上Connection: keep-alive请求头也保持不了长连接。
         * 如果HTTP/1.1版本的http请求报文不希望使用长连接,则要在请求头中加上Connection: close,接收到这个请求头的对端服务就会主动关闭连接。
         * 但是http长连接会一直保持吗?肯定是不会的。一般服务端都会设置keep-alive超时时间。超过指定的时间间隔,服务端就会主动关闭连接。
         * 同时服务端还会设置一个参数叫最大请求数,比如当最大请求数是300时,只要请求次数超过300次,即使还没到超时时间,服务端也会主动关闭连接。
         */
        if (isKeepAlive(request)) {
            response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        }
        
        ctx.write(response);
        ChannelFuture sendFileFuture;
        // 文件发送
        sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());
        
        // 添加一个异步监听,
        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
            @Override
                /**
    			 * Invoked when the operation has progressed.
   				 *
    			 * @param progress the progress of the operation so far (cumulative)
    			 * @param total the number that signifies the end of the operation when {@code progress} reaches at it.
    			 *              {@code -1} if the end of operation is unknown.
     			*/
            public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
                if (total < 0) { // total unknown
                    System.err.println("Transfer progress: " + progress);
                } else {
                    System.err.println("Transfer progress: " + progress + " / " + total);
                }
            }
			// 操作完成
            @Override
            public void operationComplete(ChannelProgressiveFuture future)
                    throws Exception {
                System.out.println("Transfer complete.");
            }
        });
        ChannelFuture lastContentFuture = ctx
                .writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        if (!isKeepAlive(request)) {
            lastContentFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }
	
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        if (ctx.channel().isActive()) {
            sendError(ctx, INTERNAL_SERVER_ERROR);
        }
    }

    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();
            }
        }
        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(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 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(HTTP_1_1, FOUND);
        response.headers().set(LOCATION, newUri);
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(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);
    }

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

3. xmind

请添加图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值