一.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