最近在做一个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的实现方案,后续再做更新阐述。