mina作为HttpServer的一种实现方案

最近在做一个mina架构的系统方案,其中mina最为中转服务器,对接自己的HttpServer端,本文以代码示例讲解下mina作为HttpServer的一种实现方案。
实现思路:利用mina TCP侦听端口的方式,监听8080端口消息,对8080端口发来的消息进行解析,在解析数据前先通过编码器ServerProtocolHTTPDecoder中decodable方法确认消息是否可以使用编码器进行解码,如果不能则进行解析。如能解析,则解析数据并返回相应结果。
本方案其不依赖任何第三方框架,运行时也不需要类似Tomcat的服务器。

主要代码:

/**
 * 服务启动类
 *
 * @see 服务启动后(即执行这里的main方法),可用浏览器访问下面两个地址
 * @see http://127.0.0.1:8000/login(浏览器会显示这几个字:登录成功)
 */

public class MainApp {

    public static void main(String[] args) throws IOException {
        NioSocketAcceptor acceptor = new NioSocketAcceptor();
        acceptor.setBacklog(0);
        acceptor.setReuseAddress(true);
        acceptor.getSessionConfig().setWriteTimeout(10000);
        acceptor.getSessionConfig().setBothIdleTime(90);
        acceptor.getFilterChain()
                .addLast("codec", new ProtocolCodecFilter(new ServerProtocolCodecFactory()));
        acceptor.getFilterChain().addLast("executor", new ExecutorFilter());
        acceptor.setHandler(new ServerHandler());
        //服务端绑定端口,8080用于接收并处理HTTP请求
        List<SocketAddress> socketAddresses = new ArrayList<SocketAddress>();
        socketAddresses.add(new InetSocketAddress(8080));
        acceptor.bind(socketAddresses);
        //判断服务端启动与否
        if (acceptor.isActive()) {
            System.out.println("写 超 时: 10000ms");
            System.out.println("发呆配置: Both Idle 90s");
            System.out.println("端口重用: true");
            System.out.println("服务端初始化完成......");
            System.out.println("服务已启动....开始监听...." + acceptor.getLocalAddresses());
        } else {
            System.out.println("服务端初始化失败......");
        }
    }
}

组装服务端的编解码器的工厂类: 

/**
 * 组装服务端的编解码器的工厂
 * @see 暂不提供客户端编解码器(其实它与服务端的编解码器差不多)
 * @see ====================================================================================
 * @see 其内部维护了一个MessageDecoder数组,用于保存添加的所有解码器
 * @see 每次decode()的时候就调用每个MessageDecoder的decodable()逐个检查
 * @see 只要发现一个MessageDecoder不是对应的解码器,就从数组中移除,知道找到合适的MessageDecoder
 * @see 如果最后发现数组为空,就表示没有找到对应的MessageDecoder,最后抛出异常
 * @see ====================================================================================
 */

public class ServerProtocolCodecFactory extends DemuxingProtocolCodecFactory {
    public ServerProtocolCodecFactory(){
        super.addMessageEncoder(String.class, ServerProtocolEncoder.class);
        super.addMessageDecoder(ServerProtocolHTTPDecoder.class);
    }
}

业务分发类:

/**
 * 业务分发类
 */

public class ServerHandler extends IoHandlerAdapter {
    @Override
    public void messageReceived(IoSession session, Object message) throws Exception {
        String respData = null;
        SzyMessage szyMessage = (SzyMessage) message;
        /*
         * 打印收到的原始报文
         */
        System.out.println("渠道:" + szyMessage.getBusiType() + "  交易码:" + szyMessage.getBusiCode() +
                "  完整报文(HEX):"
                + MinaUtil.buildHexStringWithASCII(
                MinaUtil.getBytes(szyMessage.getFullMessage(), "UTF-8")));

        StringBuilder sb = new StringBuilder();
        sb.append(
                "\r\n------------------------------------------------------------------------------------------");
        sb.append("\r\n【通信双方】").append(session);
        sb.append("\r\n【收发标识】Receive");
        sb.append("\r\n【报文内容】").append(szyMessage.getFullMessage());
        sb.append(
                "\r\n------------------------------------------------------------------------------------------");
        System.out.println(sb.toString());
        /*
         * 根据请求的业务编码做不同的处理
         */
        if (szyMessage.getBusiCode().equals("/")) {
            respData = this.buildHTTPResponseMessage("<h2>欢迎访问由Mina2.0.7编写的Web服务器</h2>");
        } else if (szyMessage.getBusiCode().equals("/favicon.ico")) {
            respData = this.buildHTTPResponseMessage(
                    "<link rel=\"icon\" href=\"https://epay.10010.com/per/favicon.ico\""
                            + "type=\"image/x-icon\"/>\n<link rel=\"shortcut icon\" href=\"http"
                            + "s://epay.10010.com/per/favicon.ico\" type=\"image/x-icon\"/>");
        } else if (szyMessage.getBusiCode().equals("/login")) {
            System.out.println("收到请求参数=[" + szyMessage.getBusiMessage() + "]");
            respData = this.buildHTTPResponseMessage("登录成功");
        } else if (szyMessage.getBusiCode().equals("10005")) {
            System.out.println("收到请求参数=[" + szyMessage.getBusiMessage() + "]");
            respData = "00003099999999`20130707144028`";
        } else {
            if (szyMessage.getBusiType().equals(SzyMessage.BUSI_TYPE_TCP)) {
                respData = "ILLEGAL_REQUEST";
            } else if (szyMessage.getBusiType().equals(SzyMessage.BUSI_TYPE_HTTP)) {
                respData = this.buildHTTPResponseMessage(501, null);
            }
        }

        /*
         * 打印应答报文
         */
        sb.setLength(0);
        sb.append("\r\n------------------------------------------------------------------------");
        sb.append("\r\n【通信双方】").append(session);
        sb.append("\r\n【收发标识】Response");
        sb.append("\r\n【报文内容】").append(respData);
        sb.append("\r\n------------------------------------------------------------------------");
        System.out.println(sb.toString());
        session.write(respData);
    }
    @Override
    public void messageSent(IoSession session, Object message) throws Exception {
        System.out.println("已回应给Client");
        if (session != null) {
            session.close(true);
        }
    }

    @Override
    public void sessionIdle(IoSession session, IdleStatus status) {
        System.out.println("请求进入闲置状态....回路即将关闭....");
        session.close(true);
    }

    @Override
    public void exceptionCaught(IoSession session, Throwable cause) {
        System.out.println("请求处理遇到异常....回路即将关闭....");
        cause.printStackTrace();
        session.close(true);
    }

    /**
     * 构建HTTP响应报文
     *
     * @param httpResponseMessageBody HTTP响应报文体
     * @return 包含了HTTP响应报文头和报文体的完整报文
     * @see 该方法默认构建的是HTTP响应码为200的响应报文
     */

    private String buildHTTPResponseMessage(String httpResponseMessageBody) {
        return buildHTTPResponseMessage(HttpURLConnection.HTTP_OK, httpResponseMessageBody);
    }

    /**
     * 构建HTTP响应报文
     *
     * @param httpResponseCode        HTTP响应码
     * @param httpResponseMessageBody HTTP响应报文体
     * @return 包含了HTTP响应报文头和报文体的完整报文
     * @see 200--请求已成功,请求所希望的响应头或数据体将随此响应返回..即服务器已成功处理了请求
     * @see 400--由于包含语法错误,当前请求无法被服务器理解..除非进行修改,否则客户端不应该重复提交这个请求..即错误请求
     * @see 500--服务器遇到了一个未曾预料的状况,导致其无法完成对请求的处理..一般来说,该问题都会在服务器的程序码出错时出现..即服务器内部错误
     * @see 501--服务器不支持当前请求所需要的某个功能..当服务器无法识别请求的方法,且无法支持其对任何资源的请求时,可能返回此代码..即尚未实施
     */

    private String buildHTTPResponseMessage(int httpResponseCode, String httpResponseMessageBody) {
        if (httpResponseCode == HttpURLConnection.HTTP_OK) {
            StringBuilder sb = new StringBuilder();
            sb.append(
                    "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: ");
            sb.append(MinaUtil.getBytes(httpResponseMessageBody, "UTF-8").length);
            sb.append("\r\n\r\n");
            sb.append(httpResponseMessageBody);
            return sb.toString();
        }

        if (httpResponseCode == HttpURLConnection.HTTP_BAD_REQUEST) {
            return "HTTP/1.1 400 Bad Request";
        }

        if (httpResponseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
            return "HTTP/1.1 500 Internal Server Error";
        }
        return "HTTP/1.1 501 Not Implemented";
    }
}

Server端编码器


/**
 * Server端协议编码器
 *
 * @see 用于编码响应给Client的报文(报文编码一律采用UTF-8)
 */

public class ServerProtocolEncoder implements MessageEncoder<String> {
    @Override
    public void encode(IoSession session, String message, ProtocolEncoderOutput out) throws
            Exception {
        IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);
        buffer.putString(message, Charset.forName("UTF-8").newEncoder());
        buffer.flip();
        out.write(buffer);
    }
}

Server端解码器



/**
 * Server端HTTP协议解码器
 *
 * @see 用于解码接收到的HTTP请求报文(报文编码一律采用UTF-8)
 */

public class ServerProtocolHTTPDecoder implements MessageDecoder {
    @Override
    public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
        if (in.remaining() < 5) {
            return MessageDecoderResult.NEED_DATA;
        }
        //服务端启动时已绑定8000端口,专门用来处理HTTP请求的
        if (session.getLocalAddress().toString().contains(":8080")) {
            return this.isComplete(in) ? MessageDecoderResult.OK : MessageDecoderResult.NEED_DATA;
        } else {
            return MessageDecoderResult.NOT_OK;
        }
    }

    @Override
    public MessageDecoderResult decode(IoSession session, IoBuffer in,
            ProtocolDecoderOutput out) throws Exception {
        byte[] message = new byte[in.limit()];
        in.get(message);
        String fullMessage = MinaUtil.getString(message, "UTF-8");
        SzyMessage szyMessage = new SzyMessage();
        szyMessage.setBusiCharset("UTF-8");
        szyMessage.setBusiType(SzyMessage.BUSI_TYPE_HTTP);
        szyMessage.setFullMessage(fullMessage);
        if (fullMessage.startsWith("GET")) {
            if (fullMessage.startsWith("GET / HTTP/1.1")) {
                szyMessage.setBusiCode("/");
            } else if (fullMessage.startsWith("GET /favicon.ico HTTP/1.1")) {
                szyMessage.setBusiCode("/favicon.ico");
            } else {
                //GET /login?aa=bb&cc=dd&ee=ff HTTP/1.1
                if (fullMessage.substring(4, fullMessage.indexOf("\r\n")).contains("?")) {
                    szyMessage.setBusiCode(fullMessage.substring(4, fullMessage.indexOf("?")));
                    szyMessage.setBusiMessage(fullMessage.substring(fullMessage.indexOf("?") + 1,
                            fullMessage.indexOf("HTTP/1.1") - 1));
                    //GET /login HTTP/1.1
                } else {
                    szyMessage
                            .setBusiCode(fullMessage.substring(4, fullMessage.indexOf("HTTP") - 1));
                }
            }
        } else if (fullMessage.startsWith("POST")) {
            //先获取到请求报文头中的Content-Length
            int contentLength = 0;
            if (fullMessage.contains("Content-Length:")) {
                String msgLenFlag =
                        fullMessage.substring(fullMessage.indexOf("Content-Length:") + 15);
                if (msgLenFlag.contains("\r\n")) {
                    contentLength = Integer.parseInt(
                            msgLenFlag.substring(0, msgLenFlag.indexOf("\r\n")).trim());
                    if (contentLength > 0) {
                        szyMessage.setBusiMessage(fullMessage.split("\r\n\r\n")[1]);
                    }
                }
            }
            if (fullMessage.substring(5, fullMessage.indexOf("\r\n")).contains("?")) {
                szyMessage.setBusiCode(fullMessage.substring(5, fullMessage.indexOf("?")));
                String urlParam = fullMessage.substring(fullMessage.indexOf("?") + 1,
                        fullMessage.indexOf("HTTP/1.1") - 1);
                if (contentLength > 0) {
                    szyMessage.setBusiMessage(urlParam + "`" + fullMessage.split("\r\n\r\n")[1]);
                } else {
                    szyMessage.setBusiMessage(urlParam);
                }
                //POST /login HTTP/1.1
            } else {
                szyMessage
                        .setBusiCode(fullMessage.substring(5, fullMessage.indexOf("HTTP/1.1") - 1));
            }
        }
        out.write(szyMessage);
        return MessageDecoderResult.OK;
    }

    @Override
    public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {
        //暂时什么都不做
    }

    /**
     * 校验HTTP请求报文是否已完整接收
     * <p>
     * 目前仅授理GET和POST请求
     *
     * @param in 装载HTTP请求报文的IoBuffer
     */
    private boolean isComplete(IoBuffer in) {
        /*
         * 先获取HTTP请求的原始报文
         */
        byte[] messages = new byte[in.limit()];
        in.get(messages);
        String message = MinaUtil.getString(messages, "UTF-8");
        /*
         * 授理GET请求
         */
        if (message.startsWith("GET")) {
            return message.endsWith("\r\n\r\n");
        }
        /*
         * 授理POST请求
         */
        if (message.startsWith("POST")) {
            if (message.contains("Content-Length:")) {
                //取Content-Length后的字符串
                String msgLenFlag = message.substring(message.indexOf("Content-Length:") + 15);
                if (msgLenFlag.contains("\r\n")) {
                    //取Content-Length值
                    int contentLength = Integer.parseInt(
                            msgLenFlag.substring(0, msgLenFlag.indexOf("\r\n")).trim());
                    if (contentLength == 0) {
                        return true;
                    } else if (contentLength > 0) {
                        //取HTTP_POST请求报文体
                        String messageBody = message.split("\r\n\r\n")[1];
                        if (contentLength == MinaUtil.getBytes(messageBody, "UTF-8").length) {
                            return true;
                        }
                    }
                }
            }
        }

        /*
         * 仅授理GET和POST请求
         */
        return false;
    }
}

工具类

/**
 * 工具类
 *
 * @author xc
 */
public class MinaUtil {
    private MinaUtil() {
    }

    /**
     * 判断输入的字符串参数是否为空
     *
     * @return boolean 空则返回true,非空则flase
     */
    public static boolean isEmpty(String input) {
        return null == input || 0 == input.length() || 0 == input.replaceAll("\\s", "").length();
    }

    /**
     * 判断输入的字节数组是否为空
     *
     * @return boolean 空则返回true,非空则flase
     */
    public static boolean isEmpty(byte[] bytes) {
        return null == bytes || 0 == bytes.length;
    }

    /**
     * 字节数组转为字符串
     *
     * @see 如果系统不支持所传入的<code>charset</code>字符集,则按照系统默认字符集进行转换
     */
    public static String getString(byte[] data, String charset) {
        if (isEmpty(data)) {
            return "";
        }
        if (isEmpty(charset)) {
            return new String(data);
        }
        try {
            return new String(data, charset);
        } catch (UnsupportedEncodingException e) {
            System.out.println("将byte数组[" + data + "]转为String时发生异常:系统不支持该字符集[" + charset + "]");
            return new String(data);
        }
    }

    /**
     * 字符串转为字节数组
     *
     * @see 如果系统不支持所传入的<code>charset</code>字符集,则按照系统默认字符集进行转换
     */

    public static byte[] getBytes(String data, String charset) {
        data = (data == null ? "" : data);
        if (isEmpty(charset)) {
            return data.getBytes();
        }

        try {
            return data.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            System.out.println("将字符串[" + data + "]转为byte[]时发生异常:系统不支持该字符集[" + charset + "]");
            return data.getBytes();
        }
    }

    /**
     * 通过ASCII码将十进制的字节数组格式化为十六进制字符串
     *
     * @see 该方法会将字节数组中的所有字节均格式化为字符串
     * @see 使用说明详见<code>formatToHexStringWithASCII(byte[], int, int)</code>方法
     */
    public static String buildHexStringWithASCII(byte[] data) {
        return buildHexStringWithASCII(data, 0, data.length);
    }

    /**
     * 通过ASCII码将十进制的字节数组格式化为十六进制字符串
     *
     * @param data   十进制的字节数组
     * @param offset 数组下标,标记从数组的第几个字节开始格式化输出
     * @param length 格式长度,其不得大于数组长度,否则抛出java.lang.ArrayIndexOutOfBoundsException
     * @return 格式化后的十六进制字符串
     * @see 该方法常用于字符串的十六进制打印,打印时左侧为十六进制数值,右侧为对应的字符串原文
     * @see 在构造右侧的字符串原文时,该方法内部使用的是平台的默认字符集,来解码byte[]数组
     * @see 该方法在将字节转为十六进制时,默认使用的是<code>java.util.Locale.getDefault()</code>
     * @see 详见String.format(String, Object...)方法和new String(byte[], int, int)构造方法
     */

    public static String buildHexStringWithASCII(byte[] data, int offset, int length) {
        int end = offset + length;
        StringBuilder sb = new StringBuilder();
        StringBuilder sb2 = new StringBuilder();
        sb.append("\r\n------------------------------------------------------------------------");
        boolean chineseCutFlag = false;
        for (int i = offset; i < end; i += 16) {
            sb.append(String.format("\r\n%04X: ", i - offset)); //X或x表示将结果格式化为十六进制整数
            sb2.setLength(0);
            for (int j = i; j < i + 16; j++) {
                if (j < end) {
                    byte b = data[j];
                    if (b >= 0) { //ENG ASCII
                        sb.append(String.format("%02X ", b));
                        if (b < 32 || b > 126) { //不可见字符
                            sb2.append(" ");
                        } else {
                            sb2.append((char) b);
                        }
                    } else { //CHA ASCII
                        if (j == i + 15) { //汉字前半个字节
                            sb.append(String.format("%02X ", data[j]));
                            chineseCutFlag = true;
                            String s = new String(data, j, 2);
                            sb2.append(s);
                        } else if (j == i && chineseCutFlag) { //后半个字节
                            sb.append(String.format("%02X ", data[j]));
                            chineseCutFlag = false;
                            String s = new String(data, j, 1);
                            sb2.append(s);
                        } else {
                            sb.append(String.format("%02X %02X ", data[j], data[j + 1]));
                            String s = new String(data, j, 2);
                            sb2.append(s);
                            j++;
                        }
                    }
                } else {
                    sb.append("   ");
                }
            }
            sb.append("| ");
            sb.append(sb2.toString());
        }
        sb.append("\r\n------------------------------------------------------------------------");
        return sb.toString();
    }
}

封装客户端请求报文 

/**
 * 封装客户端请求报文
 */

public class SzyMessage {
    public static final String BUSI_TYPE_TCP = "TCP";
    public static final String BUSI_TYPE_HTTP = "HTTP";
    public String busiCode;    //业务码
    public String busiType;    //业务类型:TCP or HTTP
    public String busiMessage; //业务报文:TCP请求时为TCP完整报文,HTTP_POST请求时为报文体部分,HTTP_GET时为报文头第一行参数部分
    public String busiCharset; //报文字符集
    public String fullMessage; //原始完整报文(用于在日志中打印最初接收到的原始完整报文)

    public String getBusiCode() {
        return busiCode;
    }

    public void setBusiCode(String busiCode) {
        this.busiCode = busiCode;
    }

    public String getBusiType() {
        return busiType;
    }

    public void setBusiType(String busiType) {
        this.busiType = busiType;
    }

    public String getBusiMessage() {
        return busiMessage;
    }

    public void setBusiMessage(String busiMessage) {
        this.busiMessage = busiMessage;
    }

    public String getBusiCharset() {
        return busiCharset;
    }

    public void setBusiCharset(String busiCharset) {
        this.busiCharset = busiCharset;
    }

    public String getFullMessage() {
        return fullMessage;
    }

    public void setFullMessage(String fullMessage) {
        this.fullMessage = fullMessage;
    }
}

 mina作为Client的实现方案,后续再做更新阐述。

发布了142 篇原创文章 · 获赞 258 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/conconbenben/article/details/103655844