Java網絡通信基礎系列-Netty實現HTTP服務


一.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.pnghttp://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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM