一.Netty实现HTTP服务
HTTP程序开发: 在进行WEB开发过程之中,HTTP是主要的通讯协议 ,但是你千万要记住一个问题,HTTP都是基于TCP协议的一种应用,HTTP是在TCP的基础上完善出来的。 TCP是一种可靠的连接协议,所以TCP的执行性能未必会高。据说google正在开发HTTP 3.0技术标准,并且这个技术里面将使用UDP协议作为HTTP基础协议 。
HTTP里面存在有请求模式:
A:HTTP 1.0:GET、POST、HEAD
B:HTTP 1.1:OPTIONS、PUT、DELETE、TRACE、CONNECT,像Restful架构里面就认为这样的请求模式的区分操作非常的明智,但是从另外一个角度来讲,这样的操作实际上会非常麻烦,所以到了Spring实现的Restful架构的时候就不推荐这样的设计了。
HTTP在进行处理的时候操作分为两类数据内容:真实请求数据、头部信息(语言、浏览器内核、Cookie)。
在进行HTTP处理的时候还需要考虑到回应的操作问题:response里面给定的状态码。
Netty可以实现HTTP协议开发,但是需要注意的是,在Netty之中服务器的开发需要考虑两种不同的状态: 处理请求:HttpRequest 处理数据:HttpContent
处理Session与Cookie 服务器端应该进行session的统一管理,应该建立HttpSession的操作标准 需要设置保存在客户端的Sesson的Cookie名称。
HTTP客户端种类: 普通用户:浏览器; 正常开发者:HttpClient、Netty 数据流底层协议开发:Uri程序类完成处理。
二.使用Netty实现了一个基础的HTTP服务器开发,实现请求信息返回
HttpServerMain.java
package com.bijian.http.server.main; import com.bijian.http.server.HttpServer; public class HttpServerMain { public static void main(String[] args) throws Exception { new HttpServer().run(); } }
HttpServer.java
package com.bijian.http.server; import com.bijian.http.server.handler.HttpServerHandler; import com.bijian.info.HostInfo; 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.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; public class HttpServer { public void run() throws Exception { // 线程池是提升服务器性能的重要技术手段,利用定长的线程池可以保证核心线程的有效数量 // 在Netty之中线程池的实现分为两类:主线程池(接收客户端连接)、工作线程池(处理客户端连接) EventLoopGroup bossGroup = new NioEventLoopGroup(10); // 创建接收线程池 EventLoopGroup workerGroup = new NioEventLoopGroup(20); // 创建工作线程池 System.out.println("服务器启动成功,监听端口为:" + HostInfo.PORT); try { // 创建一个服务器端的程序类进行NIO启动,同时可以设置Channel ServerBootstrap serverBootstrap = new ServerBootstrap(); // 服务器端 // 设置要使用的线程池以及当前的Channel类型 serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class); // 接收到信息之后需要进行处理,于是定义子处理器 serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new HttpResponseEncoder()) ; // 响应编码 socketChannel.pipeline().addLast(new HttpRequestDecoder()) ; // 请求解码 socketChannel.pipeline().addLast(new HttpServerHandler()) ; } }); // 可以直接利用常亮进行TCP协议的相关配置 serverBootstrap.option(ChannelOption.SO_BACKLOG, 128); serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); // ChannelFuture描述的时异步回调的处理操作 ChannelFuture future = serverBootstrap.bind(HostInfo.PORT).sync(); future.channel().closeFuture().sync();// 等待Socket被关闭 } finally { workerGroup.shutdownGracefully() ; bossGroup.shutdownGracefully() ; } } }
HttpServerHandler.java
package com.bijian.http.server.handler; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.*; import io.netty.util.CharsetUtil; public class HttpServerHandler extends ChannelInboundHandlerAdapter { private HttpRequest request; private DefaultFullHttpResponse response ; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpRequest) { // 实现HTTP请求处理操作 this.request = (HttpRequest) msg; // 获取Request对象 System.out.println("【Netty-HTTP服务器端】uri = " + this.request.uri() + ",Method = " + this.request.method() + ",Headers = " + request.headers()); String content = "<html>" + " <head>" + " <title>Hello Netty</title>" + " </head>" + " <body>" + " <h1>好好学习,天天向上</h1>" + " </body>" + "</html>"; // HTTP服务器可以回应的数据就是HTML代码 this.responseWrite(ctx,content); } } private void responseWrite(ChannelHandlerContext ctx,String content) { ByteBuf buf = Unpooled.copiedBuffer(content,CharsetUtil.UTF_8) ; this.response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,buf) ; this.response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/html;charset=UTF-8") ; // 设置MIME类型 this.response.headers().set(HttpHeaderNames.CONTENT_LENGTH,String.valueOf(buf.readableBytes())) ; // 设置回应数据长度 ctx.writeAndFlush(this.response).addListener(ChannelFutureListener.CLOSE) ; // 数据回应完毕之后进行操作关闭 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
启动服务,在浏览器中输入:http://localhost:9999/,运行效果如下
三.实现HTTP访问下的Cookie与Session管理机制
HttpSession.java
package com.bijian.http.server.component; /** * 与JavaWEB开发接口标准完全同步 */ public interface HttpSession { public static final String SESSIONID = "XiaoLilLaoShiSessionId" ; public Object getAttribute(String name) ; public void setAttribute(String name, Object value) ; public void removeAttribute(String name) ; public String getId() ; public void invalidate() ; }
DefaultHttpSession.java
package com.bijian.http.server.component.impl; import com.bijian.http.server.component.HttpSession; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class DefaultHttpSession implements HttpSession { private String sessionId; private Map<String, Object> attributes = new HashMap<String, Object>(); public DefaultHttpSession() { this.sessionId = UUID.randomUUID().toString() ; // 随机生成一个 SessionId } @Override public Object getAttribute(String name) { return this.attributes.get(name); } @Override public void setAttribute(String name, Object value) { this.attributes.put(name,value) ; } @Override public void removeAttribute(String name) { this.attributes.remove(name) ; } @Override public String getId() { return this.sessionId; } @Override public void invalidate() { this.sessionId = null ; } }
HttpSessionManager.java
package com.bijian.http.server.component; import com.bijian.http.server.component.impl.DefaultHttpSession; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 实现一个session操作的并发管理程序类 */ public class HttpSessionManager { // 在一个Http服务器的进程之中,只允许存在有一个Session保存集合 // 使用ConcurrentHashMap是因为这个子类会将集合进行分片保存,每一段的数据多线程同步,而不同段进行异步操作 private static final Map<String, HttpSession> SESSION_MAP = new ConcurrentHashMap<String, HttpSession>(); /** * 每当有用户连接的时候就需要创建一个SessionId的数据内容 * @return sessionId */ public static String createSession() { HttpSession session = new DefaultHttpSession() ; // 获取了一个SessionId String sessionId = session.getId() ; // HttpSession实现接口 SESSION_MAP.put(sessionId,session) ;// 实现数据集合保存 return sessionId ; } /** * 判断当前的SessionId是否存在于集合之中 * @param sessionId * @return */ public static boolean isExists(String sessionId) { if(SESSION_MAP.containsKey(sessionId)) { HttpSession session = SESSION_MAP.get(sessionId) ; if (session.getId() == null) { // 该Session已经被销毁了 SESSION_MAP.remove(sessionId) ; return false ; } return true ; } else { return false ; } } public static void invalidate(String sessionId) { SESSION_MAP.remove(sessionId) ; } public static HttpSession getSession(String sessionId) { return SESSION_MAP.get(sessionId) ; } }
HttpServerHandler.java
package com.bijian.http.server.handler; import com.bijian.http.server.component.HttpSession; import com.bijian.http.server.component.HttpSessionManager; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.ServerCookieDecoder; import io.netty.handler.codec.http.cookie.ServerCookieEncoder; import io.netty.util.CharsetUtil; import java.util.Iterator; import java.util.Set; public class HttpServerHandler extends ChannelInboundHandlerAdapter { private HttpRequest request; private DefaultFullHttpResponse response ; private HttpSession session ; /** * 依据传入的标记内容进行是否向客户端Cookie中保存有SessionId数据的操作 * @param exists */ private void setSessionId(boolean exists) { if(exists == false) { // 用户发送来的头信息里面不包含有SessionId内容 String encodeCookie = ServerCookieEncoder.STRICT.encode(HttpSession.SESSIONID, HttpSessionManager.createSession()) ; this.response.headers().set(HttpHeaderNames.SET_COOKIE,encodeCookie) ;// 客户端保存Cookie数据 } } /** * 当前所发送的请求里面是否存在有指定的 SessionID数据信息 * @return 如果存在返回true,否则返回false */ public boolean isHasSessionId() { String cookieStr = this.request.headers().get(HttpHeaderNames.COOKIE) ; // 获取客户端头信息发送来的Cookie数据 if (cookieStr == null || "".equals(cookieStr)) { return false ; } Set<Cookie> cookieSet = ServerCookieDecoder.STRICT.decode(cookieStr); Iterator<Cookie> iter = cookieSet.iterator() ; while(iter.hasNext()) { Cookie cookie = iter.next() ; if(HttpSession.SESSIONID.equals(cookie.name())) { if (HttpSessionManager.isExists(cookie.value())) { this.session = HttpSessionManager.getSession(cookie.value()) ; return true ; } } } return false ; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpRequest) { // 实现HTTP请求处理操作 this.request = (HttpRequest) msg; // 获取Request对象 System.out.println("【Netty-HTTP服务器端】uri = " + this.request.uri() + ",Method = " + this.request.method() + ",Headers = " + request.headers()); String content = "<html>" + " <head>" + " <title>Hello Netty</title>" + " </head>" + " <body>" + " <h1>好好学习,天天向上</h1>" + " </body>" + "</html>"; // HTTP服务器可以回应的数据就是HTML代码 this.responseWrite(ctx,content); } } private void responseWrite(ChannelHandlerContext ctx,String content) { ByteBuf buf = Unpooled.copiedBuffer(content,CharsetUtil.UTF_8) ; this.response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,buf) ; this.response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/html;charset=UTF-8") ; // 设置MIME类型 this.response.headers().set(HttpHeaderNames.CONTENT_LENGTH,String.valueOf(buf.readableBytes())) ; // 设置回应数据长度 this.setSessionId(this.isHasSessionId()); ctx.writeAndFlush(this.response).addListener(ChannelFutureListener.CLOSE) ; // 数据回应完毕之后进行操作关闭 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
启动服务器,在浏览器输入:http://localhost:9999/,运行效果如下:
测试发现,这个Name为XiaoLilLaoShiSessionId的Session的Value在第一次请求后生成,后面刷新网页是不变的,即成功实现HTTP访问下的Cookie与Session管理机制。
四.使用Netty访问Http服务器的客户端
HttpClientMain.java
package com.bijian.http.client.main; import com.bijian.http.client.HttpClient; public class HttpClientMain { public static void main(String[] args) throws Exception { new HttpClient().run(); } }
HttpClient.java
package com.bijian.http.client; import com.bijian.http.client.handler.HttpClientHandler; import com.bijian.info.HostInfo; import io.netty.bootstrap.Bootstrap; 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.NioSocketChannel; import io.netty.handler.codec.http.*; public class HttpClient { public void run() throws Exception { // 1、如果现在客户端不同,那么也可以不使用多线程模式来处理; // 在Netty中考虑到代码的统一性,也允许你在客户端设置线程池 EventLoopGroup group = new NioEventLoopGroup(); // 创建一个线程池 try { Bootstrap client = new Bootstrap(); // 创建客户端处理程序 client.group(group).channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new HttpResponseDecoder()); // 追加了处理器 socketChannel.pipeline().addLast(new HttpRequestEncoder()); // 追加了处理器 socketChannel.pipeline().addLast(new HttpClientHandler()); // 追加了处理器 } }); ChannelFuture channelFuture = client.connect(HostInfo.HOST_NAME, HostInfo.PORT).sync(); String url = "http://" + HostInfo.HOST_NAME + ":" + HostInfo.PORT ; // HTTP访问地址 DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,HttpMethod.GET,url) ; request.headers().set(HttpHeaderNames.HOST,"127.0.0.1") ; request.headers().set(HttpHeaderNames.CONNECTION,HttpHeaderValues.KEEP_ALIVE) ; request.headers().set(HttpHeaderNames.CONTENT_LENGTH,String.valueOf(request.content().readableBytes())) ; request.headers().set(HttpHeaderNames.COOKIE,"nothing") ; channelFuture.channel().writeAndFlush(request) ; // 发送请求 channelFuture.channel().closeFuture().sync(); // 关闭连接 } finally { group.shutdownGracefully(); } } }
HttpClientHandler.java
package com.bijian.http.client.handler; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.cookie.ServerCookieDecoder; import io.netty.util.CharsetUtil; public class HttpClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpResponse) { HttpResponse response = (HttpResponse) msg; System.out.println("【Netty-Http客户端】ContenType = " + response.headers().get(HttpHeaderNames.CONTENT_TYPE)); System.out.println("【Netty-Http客户端】ContentLength = " + response.headers().get(HttpHeaderNames.CONTENT_LENGTH)); System.out.println("【Netty-Http客户端】SET-COOKIE = " + ServerCookieDecoder.STRICT.decode(response.headers().get(HttpHeaderNames.SET_COOKIE))); } if (msg instanceof HttpContent) { HttpContent content = (HttpContent) msg ; ByteBuf buf = content.content() ; // 获取数据信息 System.out.println("【Netty-HTTP客户端】" + buf.toString(CharsetUtil.UTF_8)); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
先启动服务端,再启动客户端,运行效果如下:
五.使用Netty实现Http协议下的图片传输与显示处理
HttpServer.java
package com.bijian.http.server; import com.bijian.http.server.handler.HttpServerHandler; import com.bijian.info.HostInfo; 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.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; import io.netty.handler.codec.http.multipart.DiskFileUpload; import io.netty.handler.stream.ChunkedWriteHandler; public class HttpServer { static { DiskFileUpload.baseDirectory = System.getProperty("user.dir") + "/echo-http/upload/" ; } public void run() throws Exception { // 线程池是提升服务器性能的重要技术手段,利用定长的线程池可以保证核心线程的有效数量 // 在Netty之中线程池的实现分为两类:主线程池(接收客户端连接)、工作线程池(处理客户端连接) EventLoopGroup bossGroup = new NioEventLoopGroup(10); // 创建接收线程池 EventLoopGroup workerGroup = new NioEventLoopGroup(20); // 创建工作线程池 System.out.println("服务器启动成功,监听端口为:" + HostInfo.PORT); try { // 创建一个服务器端的程序类进行NIO启动,同时可以设置Channel ServerBootstrap serverBootstrap = new ServerBootstrap(); // 服务器端 // 设置要使用的线程池以及当前的Channel类型 serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class); // 接收到信息之后需要进行处理,于是定义子处理器 serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new HttpResponseEncoder()) ; // 响应编码 socketChannel.pipeline().addLast(new HttpRequestDecoder()) ; // 请求解码 socketChannel.pipeline().addLast(new ChunkedWriteHandler()) ; // 图片传输处理器 socketChannel.pipeline().addLast(new HttpServerHandler()) ; } }); // 可以直接利用常亮进行TCP协议的相关配置 serverBootstrap.option(ChannelOption.SO_BACKLOG, 128); serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); // ChannelFuture描述的时异步回调的处理操作 ChannelFuture future = serverBootstrap.bind(HostInfo.PORT).sync(); future.channel().closeFuture().sync();// 等待Socket被关闭 } finally { workerGroup.shutdownGracefully() ; bossGroup.shutdownGracefully() ; } } }
HttpServerHandler.java
package com.bijian.http.server.handler; import com.bijian.http.server.component.HttpSession; import com.bijian.http.server.component.HttpSessionManager; 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.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.ServerCookieDecoder; import io.netty.handler.codec.http.cookie.ServerCookieEncoder; import io.netty.handler.codec.http.multipart.DiskFileUpload; import io.netty.handler.stream.ChunkedFile; import io.netty.util.CharsetUtil; import javax.activation.MimetypesFileTypeMap; import java.io.File; import java.util.Iterator; import java.util.Set; public class HttpServerHandler extends ChannelInboundHandlerAdapter { private HttpRequest request; private DefaultFullHttpResponse response ; private HttpSession session ; private ChannelHandlerContext ctx ; /** * 依据传入的标记内容进行是否向客户端Cookie中保存有SessionId数据的操作 * @param exists */ private void setSessionId(boolean exists) { if(exists == false) { // 用户发送来的头信息里面不包含有SessionId内容 String encodeCookie = ServerCookieEncoder.STRICT.encode(HttpSession.SESSIONID, HttpSessionManager.createSession()) ; this.response.headers().set(HttpHeaderNames.SET_COOKIE,encodeCookie) ;// 客户端保存Cookie数据 } } /** * 当前所发送的请求里面是否存在有指定的 SessionID数据信息 * @return 如果存在返回true,否则返回false */ public boolean isHasSessionId() { String cookieStr = this.request.headers().get(HttpHeaderNames.COOKIE) ; // 获取客户端头信息发送来的Cookie数据 if (cookieStr == null || "".equals(cookieStr)) { return false ; } Set<Cookie> cookieSet = ServerCookieDecoder.STRICT.decode(cookieStr); Iterator<Cookie> iter = cookieSet.iterator() ; while(iter.hasNext()) { Cookie cookie = iter.next() ; if(HttpSession.SESSIONID.equals(cookie.name())) { if (HttpSessionManager.isExists(cookie.value())) { this.session = HttpSessionManager.getSession(cookie.value()) ; return true ; } } } return false ; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { this.ctx = ctx ; if (msg instanceof HttpRequest) { // 实现HTTP请求处理操作 this.request = (HttpRequest) msg; // 获取Request对象 System.out.println("【Netty-HTTP服务器端】uri = " + this.request.uri() + "、Method = " + this.request.method() + "、Headers = " + request.headers()); this.handleUrl(this.request.uri()); } } private void responseWrite(String content) { ByteBuf buf = Unpooled.copiedBuffer(content,CharsetUtil.UTF_8) ; this.response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,buf) ; this.response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/html;charset=UTF-8") ; // 设置MIME类型 this.response.headers().set(HttpHeaderNames.CONTENT_LENGTH,String.valueOf(buf.readableBytes())) ; // 设置回应数据长度 this.setSessionId(this.isHasSessionId()); ctx.writeAndFlush(this.response).addListener(ChannelFutureListener.CLOSE) ; // 数据回应完毕之后进行操作关闭 } public void info() { String content = "<html>" + " <head>" + " <title>Hello Netty</title>" + " </head>" + " <body>" + " <h1>好好学习,天天向上</h1>" + " <img src='/show.png'>" + " </body>" + "</html>"; // HTTP服务器可以回应的数据就是HTML代码 this.responseWrite(content); } public void favicon() { try { this.sendImage("favicon.ico"); } catch (Exception e) { e.printStackTrace(); } } private void sendImage(String fileName) throws Exception { String filePath = DiskFileUpload.baseDirectory + fileName ; File sendFile = new File(filePath) ; HttpResponse imageResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK) ; // imageResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,String.valueOf(sendFile.length())) ; MimetypesFileTypeMap mimeMap = new MimetypesFileTypeMap() ; imageResponse.headers().set(HttpHeaderNames.CONTENT_TYPE,mimeMap.getContentType(sendFile)) ; imageResponse.headers().set(HttpHeaderNames.CONNECTION,HttpHeaderValues.KEEP_ALIVE) ; this.ctx.writeAndFlush(imageResponse) ; this.ctx.writeAndFlush(new ChunkedFile(sendFile)) ; // 在多媒体信息发送完毕只后需要设置一个空的消息体,否则内容无法显示 ChannelFuture channelFuture = this.ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) ; channelFuture.addListener(ChannelFutureListener.CLOSE) ; } public void handleUrl(String uri) { if ("/info".equals(uri)) { this.info(); } else if ("/favicon.ico".equals(uri)) { this.favicon(); } else if ("/show.png".equals(uri)) { this.show() ; } } public void show() { try { this.sendImage("show.png"); } catch (Exception e) { e.printStackTrace(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
启动服务端,浏览器输入:http://localhost:9999/info,显示如下:
浏览器输入:http://localhost:9999/show.png或http://localhost:9999/favicon.ico,能正常下载相应的图片。
附:TestSeparator.java
package com.bijian; public class TestSeparator { public static void main(String[] args) { // System.getProperties().list(System.out); System.out.println(System.getProperty("line.separator")); System.out.println(System.getProperty("user.dir") + "/echo-http/upload/"); } }
运行结果如下:
PS:其它知识
Java基本功:合理的类设计+多线程(JUC)+反射机制+网络通讯+数据结构+JVM
Shiro、SpringDataJPA、MyBatis、OAuth软件设计方法、Linux使用需要熟练(如果你可以独立的实现一套分布式的认证于授权管理,那么就证明你的水平不低了。
架构师经常需要精通几门语言。
docker+k8s+devops。
特别说明:这是开课吧的公开课,完整代码也是讲课老师在GitHub上的代码,我仅只是下载到本地改了一下包名运行调试。完整代码及相关阶段的提交历史我Fork了一份:https://github.com/bijian1013/echo