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