8.netty开发http协议
来源:互联网 发布:java技术培训 编辑:程序博客网 时间:2024/06/08 19:55
1.介绍
http协议:使用httpUrl来确定网络资源(url是一种特殊类型的uri)
http请求包含三个部分:请求行、消息头、请求正文
HttpRequest/HttpResponse详见《Netty权威指南 第2版》p155
2.netty开发http协议
这里使用netty开发一个http文件服务器。可以通过浏览器发送htpp请求访问本工程文件
handler:
package com.tyf.netty;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.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.HttpMethod.GET;import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;import static io.netty.handler.codec.http.HttpResponseStatus.FOUND;import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;import static io.netty.handler.codec.http.HttpResponseStatus.OK;import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelFutureListener;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelProgressiveFuture;import io.netty.channel.ChannelProgressiveFutureListener;import io.netty.channel.SimpleChannelInboundHandler;import io.netty.handler.codec.http.DefaultFullHttpResponse;import io.netty.handler.codec.http.DefaultHttpResponse;import io.netty.handler.codec.http.FullHttpRequest;import io.netty.handler.codec.http.FullHttpResponse;import io.netty.handler.codec.http.HttpHeaders;import io.netty.handler.codec.http.HttpResponse;import io.netty.handler.codec.http.HttpResponseStatus;import io.netty.handler.codec.http.LastHttpContent;import io.netty.handler.stream.ChunkedFile;import io.netty.util.CharsetUtil;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 javax.activation.MimetypesFileTypeMap;/** * @author lilinfeng * @date 2014年2月14日 * @version 1.0 */public class MyServerHandler extendsSimpleChannelInboundHandler<FullHttpRequest> { private final String url; public MyServerHandler(String url) {this.url = url; } @Override public void messageReceived(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {if (!request.getDecoderResult().isSuccess()) { sendError(ctx, BAD_REQUEST); return;}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);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() { 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);} } 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(); }}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(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); } //设置response_content_type_header private static void setContentTypeHeader(HttpResponse response, File file) {MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();response.headers().set(CONTENT_TYPE,mimeTypesMap.getContentType(file.getPath())); }}
server:
package com.tyf.netty;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelHandlerAdapter;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelOption;import io.netty.channel.ChannelHandler.Sharable;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.DelimiterBasedFrameDecoder;import io.netty.handler.codec.FixedLengthFrameDecoder;import io.netty.handler.codec.LengthFieldBasedFrameDecoder;import io.netty.handler.codec.LengthFieldPrepender;import io.netty.handler.codec.http.HttpObjectAggregator;import io.netty.handler.codec.http.HttpRequestDecoder;import io.netty.handler.codec.http.HttpResponseDecoder;import io.netty.handler.codec.http.HttpResponseEncoder;import io.netty.handler.codec.protobuf.ProtobufDecoder;import io.netty.handler.codec.protobuf.ProtobufEncoder;import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;import io.netty.handler.codec.string.StringDecoder;import io.netty.handler.stream.ChunkedWriteHandler;public class Server {//创建起步程序public static void main(String [] argsStrings) throws Exception {//配置服务端NIO线程组(boss线程、worker线程)EventLoopGroup bGroup = new NioEventLoopGroup();EventLoopGroup wGroup = new NioEventLoopGroup();//创建启动辅助类ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bGroup, wGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel channel) throws Exception {//http请求解码器channel.pipeline().addLast(new HttpRequestDecoder());//将多个消息转换为单一fullHttpRequest/Response,因为http解码器在每个消息中会生成多个http对象channel.pipeline().addLast(new HttpObjectAggregator(65536));channel.pipeline().addLast(new HttpResponseEncoder());//这个用于支持大文件传输但是防止java内存溢出channel.pipeline().addLast(new ChunkedWriteHandler());channel.pipeline().addLast(new MyServerHandler("/src/main/java/com/tyf/netty"));} });try {//监听本地端口,同步等待监听结果ChannelFuture future = bootstrap.bind(11111).sync();//等待服务端监听端口关闭,优雅退出future.channel().closeFuture().sync();}finally {bGroup.shutdownGracefully();wGroup.shutdownGracefully();} }}
工程目录:
3.测试
(1)访问正确的地址:http://localhost:11111/src/main/java/com/tyf/netty/
本个文件都是文本文件可以直接在浏览器中打开查看。点击可以下载文件。
(2)访问错误的地址:
状态码403:
阅读全文