Mina as an implementation scheme of HttpServer

Recently, I am working on a system solution of mina architecture, in which mina is the most transit server and connects to my own HttpServer. This article uses code examples to explain mina as an implementation scheme of HttpServer.
Implementation idea: use the mina TCP listening port method to listen to the 8080 port message and parse the message sent from the 8080 port. Before parsing the data, first confirm whether the message can be decoded by the encoder through the decodable method in the encoder ServerProtocolHTTPDecoder. If it is not possible, it will be analyzed. If it can be parsed, the data is parsed and the corresponding result is returned.
This solution does not rely on any third-party framework and does not require a Tomcat-like server at runtime.

Main code:

/**
 * 服务启动类
 *
 * @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("服务端初始化失败......");
        }
    }
}

Factory class for assembling server-side codecs: 

/**
 * 组装服务端的编解码器的工厂
 * @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);
    }
}

Business distribution:

/**
 * 业务分发类
 */

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-side encoder


/**
 * 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 decoder



/**
 * 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;
    }
}

Tools

/**
 * 工具类
 *
 * @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();
    }
}

Encapsulate client request message 

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

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, as the client's implementation plan, will be updated and explained later.

 

Published 142 original articles · praised 258 · 160,000 views

Guess you like

Origin blog.csdn.net/conconbenben/article/details/103655844