目的:
Java平台下的内部组件之间的通信。
1.WebService 由于感觉本身Java平台下的Web Service标准就不够统一,相互之间的调用就会有一些问题,更不用说与.net等其他平台了。而且WebService也是对HTTP请求的一次封装,效率上肯定会有损失,所以就不考虑用WebService了。
2.Socket,包括Java原生的Socket API和nio,本身都很好,效率也会不错,它们之间的区别大概就是资源占用上了。但是使用Socket的通信,有几个比较复杂的地方是:1)协议解析,要订协议,解析及序列化2)粘包分包的处理(这个在长连接的情况下才会出现,可以不在考虑范围内)3)资源的管理,弄不好的话会导致CPU占用较高或者内存不知不觉泄露。
3.HTTP通信。由于应用是独立的,不能依托于Web容器。Java原生的HttpServer API好像不推荐使用(藏在好深的一个包里com.sun.net.httpserver.*)。
4.话说Mina的效率很高,是基于nio的异步通信,封装简化了好多。通过比较简单的包装就可以组成一个HTTP Server(下面例子中就是按照Mina官方提供的demo,自己改动了几点形成的)。然后HTTP的Client端也随便封装下就是了。
步骤
1.封装HTTP请求消息类和响应消息类
- package com.ajita.httpserver;
-
- import java.util.Map;
- import java.util.Map.Entry;
-
- /**
- * 使用Mina解析出的HTTP请求对象
- *
- * @author Ajita
- *
- */
- public class HttpRequestMessage {
- /**
- * HTTP请求的主要属性及内容
- */
- private Map<String, String[]> headers = null;
-
- public Map<String, String[]> getHeaders() {
- return headers;
- }
-
- public void setHeaders(Map<String, String[]> headers) {
- this.headers = headers;
- }
-
- /**
- * 获取HTTP请求的Context信息
- */
- public String getContext() {
- String[] context = headers.get("Context");
- return context == null ? "" : context[0];
- }
-
- /**
- * 根据属性名称获得属性值数组第一个值,用于在url中传递的参数
- */
- public String getParameter(String name) {
- String[] param = headers.get("@".concat(name));
- return param == null ? "" : param[0];
- }
-
- /**
- * 根据属性名称获得属性值,用于在url中传递的参数
- */
- public String[] getParameters(String name) {
- String[] param = headers.get("@".concat(name));
- return param == null ? new String[] {} : param;
- }
-
- /**
- * 根据属性名称获得属性值,用于请求的特征参数
- */
- public String[] getHeader(String name) {
- return headers.get(name);
- }
-
- @Override
- public String toString() {
- StringBuilder str = new StringBuilder();
-
- for (Entry<String, String[]> e : headers.entrySet()) {
- str.append(e.getKey() + " : " + arrayToString(e.getValue(), ',')
- + "\n");
- }
- return str.toString();
- }
-
- /**
- * 静态方法,用来把一个字符串数组拼接成一个字符串
- *
- * @param s要拼接的字符串数组
- * @param sep数据元素之间的烦恼歌负
- * @return 拼接成的字符串
- */
- public static String arrayToString(String[] s, char sep) {
- if (s == null || s.length == 0) {
- return "";
- }
- StringBuffer buf = new StringBuffer();
- if (s != null) {
- for (int i = 0; i < s.length; i++) {
- if (i > 0) {
- buf.append(sep);
- }
- buf.append(s[i]);
- }
- }
- return buf.toString();
- }
-
- }
-
- package com.ajita.httpserver;
-
-
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
-
- import org.apache.mina.core.buffer.IoBuffer;
-
- public class HttpResponseMessage {
- /** HTTP response codes */
- public static final int HTTP_STATUS_SUCCESS = 200;
-
- public static final int HTTP_STATUS_NOT_FOUND = 404;
-
- /** Map<String, String> */
- private final Map<String, String> headers = new HashMap<String, String>();
-
- /** Storage for body of HTTP response. */
- private final ByteArrayOutputStream body = new ByteArrayOutputStream(1024);
-
- private int responseCode = HTTP_STATUS_SUCCESS;
-
- public HttpResponseMessage() {
- // headers.put("Server", "HttpServer (" + Server.VERSION_STRING + ')');
- headers.put("Server", "HttpServer (" + "Mina 2.0" + ')');
- headers.put("Cache-Control", "private");
- headers.put("Content-Type", "text/html; charset=iso-8859-1");
- headers.put("Connection", "keep-alive");
- headers.put("Keep-Alive", "200");
- headers.put("Date", new SimpleDateFormat(
- "EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date()));
- headers.put("Last-Modified", new SimpleDateFormat(
- "EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date()));
- }
-
- public Map<String, String> getHeaders() {
- return headers;
- }
-
- public void setContentType(String contentType) {
- headers.put("Content-Type", contentType);
- }
-
- public void setResponseCode(int responseCode) {
- this.responseCode = responseCode;
- }
-
- public int getResponseCode() {
- return this.responseCode;
- }
-
- public void appendBody(byte[] b) {
- try {
- body.write(b);
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
-
- public void appendBody(String s) {
- try {
- body.write(s.getBytes());
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
-
- public IoBuffer getBody() {
- return IoBuffer.wrap(body.toByteArray());
- }
-
- public int getBodyLength() {
- return body.size();
- }
-
- }
2.封装Mina的解析HTTP请求和发送HTTP响应的编码类和解码类
- package com.ajita.httpserver;
-
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.StringReader;
- import java.nio.charset.CharacterCodingException;
- import java.nio.charset.Charset;
- import java.nio.charset.CharsetDecoder;
- import java.util.HashMap;
- import java.util.Map;
-
- import org.apache.mina.core.buffer.IoBuffer;
- import org.apache.mina.core.session.IoSession;
- import org.apache.mina.filter.codec.ProtocolDecoderOutput;
- import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
- import org.apache.mina.filter.codec.demux.MessageDecoderResult;
-
- public class HttpRequestDecoder extends MessageDecoderAdapter {
- private static final byte[] CONTENT_LENGTH = new String("Content-Length:")
- .getBytes();
- static String defaultEncoding;
- private CharsetDecoder decoder;
-
- public CharsetDecoder getDecoder() {
- return decoder;
- }
-
- public void setEncoder(CharsetDecoder decoder) {
- this.decoder = decoder;
- }
-
- private HttpRequestMessage request = null;
-
- public HttpRequestDecoder() {
- decoder = Charset.forName(defaultEncoding).newDecoder();
- }
-
- public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
- try {
- return messageComplete(in) ? MessageDecoderResult.OK
- : MessageDecoderResult.NEED_DATA;
- } catch (Exception ex) {
- ex.printStackTrace();
- }
-
- return MessageDecoderResult.NOT_OK;
- }
-
- public MessageDecoderResult decode(IoSession session, IoBuffer in,
- ProtocolDecoderOutput out) throws Exception {
- HttpRequestMessage m = decodeBody(in);
-
- // Return NEED_DATA if the body is not fully read.
- if (m == null) {
- return MessageDecoderResult.NEED_DATA;
- }
-
- out.write(m);
-
- return MessageDecoderResult.OK;
-
- }
-
- /*
- * 判断HTTP请求是否完整,若格式有错误直接抛出异常
- */
- private boolean messageComplete(IoBuffer in) {
- int last = in.remaining() - 1;
- if (in.remaining() < 4) {
- return false;
- }
-
- // to speed up things we check if the Http request is a GET or POST
- if (in.get(0) == (byte) 'G' && in.get(1) == (byte) 'E'
- && in.get(2) == (byte) 'T') {
- // Http GET request therefore the last 4 bytes should be 0x0D 0x0A
- // 0x0D 0x0A
- return in.get(last) == (byte) 0x0A
- && in.get(last - 1) == (byte) 0x0D
- && in.get(last - 2) == (byte) 0x0A
- && in.get(last - 3) == (byte) 0x0D;
- } else if (in.get(0) == (byte) 'P' && in.get(1) == (byte) 'O'
- && in.get(2) == (byte) 'S' && in.get(3) == (byte) 'T') {
- // Http POST request
- // first the position of the 0x0D 0x0A 0x0D 0x0A bytes
- int eoh = -1;
- for (int i = last; i > 2; i--) {
- if (in.get(i) == (byte) 0x0A && in.get(i - 1) == (byte) 0x0D
- && in.get(i - 2) == (byte) 0x0A
- && in.get(i - 3) == (byte) 0x0D) {
- eoh = i + 1;
- break;
- }
- }
- if (eoh == -1) {
- return false;
- }
- for (int i = 0; i < last; i++) {
- boolean found = false;
- for (int j = 0; j < CONTENT_LENGTH.length; j++) {
- if (in.get(i + j) != CONTENT_LENGTH[j]) {
- found = false;
- break;
- }
- found = true;
- }
- if (found) {
- // retrieve value from this position till next 0x0D 0x0A
- StringBuilder contentLength = new StringBuilder();
- for (int j = i + CONTENT_LENGTH.length; j < last; j++) {
- if (in.get(j) == 0x0D) {
- break;
- }
- contentLength.append(new String(
- new byte[] { in.get(j) }));
- }
- // if content-length worth of data has been received then
- // the message is complete
- return Integer.parseInt(contentLength.toString().trim())
- + eoh == in.remaining();
- }
- }
- }
-
- // the message is not complete and we need more data
- return false;
-
- }
-
- private HttpRequestMessage decodeBody(IoBuffer in) {
- request = new HttpRequestMessage();
- try {
- request.setHeaders(parseRequest(new StringReader(in
- .getString(decoder))));
- return request;
- } catch (CharacterCodingException ex) {
- ex.printStackTrace();
- }
-
- return null;
-
- }
-
- private Map<String, String[]> parseRequest(StringReader is) {
- Map<String, String[]> map = new HashMap<String, String[]>();
- BufferedReader rdr = new BufferedReader(is);
-
- try {
- // Get request URL.
- String line = rdr.readLine();
- String[] url = line.split(" ");
- if (url.length < 3) {
- return map;
- }
-
- map.put("URI", new String[] { line });
- map.put("Method", new String[] { url[0].toUpperCase() });
- map.put("Context", new String[] { url[1].substring(1) });
- map.put("Protocol", new String[] { url[2] });
- // Read header
- while ((line = rdr.readLine()) != null && line.length() > 0) {
- String[] tokens = line.split(": ");
- map.put(tokens[0], new String[] { tokens[1] });
- }
-
- // If method 'POST' then read Content-Length worth of data
- if (url[0].equalsIgnoreCase("POST")) {
- int len = Integer.parseInt(map.get("Content-Length")[0]);
- char[] buf = new char[len];
- if (rdr.read(buf) == len) {
- line = String.copyValueOf(buf);
- }
- } else if (url[0].equalsIgnoreCase("GET")) {
- int idx = url[1].indexOf('?');
- if (idx != -1) {
- map.put("Context",
- new String[] { url[1].substring(1, idx) });
- line = url[1].substring(idx + 1);
- } else {
- line = null;
- }
- }
- if (line != null) {
- String[] match = line.split("\\&");
- for (String element : match) {
- String[] params = new String[1];
- String[] tokens = element.split("=");
- switch (tokens.length) {
- case 0:
- map.put("@".concat(element), new String[] {});
- break;
- case 1:
- map.put("@".concat(tokens[0]), new String[] {});
- break;
- default:
- String name = "@".concat(tokens[0]);
- if (map.containsKey(name)) {
- params = map.get(name);
- String[] tmp = new String[params.length + 1];
- for (int j = 0; j < params.length; j++) {
- tmp[j] = params[j];
- }
- params = null;
- params = tmp;
- }
- params[params.length - 1] = tokens[1].trim();
- map.put(name, params);
- }
- }
- }
- } catch (IOException ex) {
- ex.printStackTrace();
- }
-
- return map;
- }
-
- }
- package com.ajita.httpserver;
-
-
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
-
- import org.apache.mina.core.buffer.IoBuffer;
-
- public class HttpResponseMessage {
- /** HTTP response codes */
- public static final int HTTP_STATUS_SUCCESS = 200;
-
- public static final int HTTP_STATUS_NOT_FOUND = 404;
-
- /** Map<String, String> */
- private final Map<String, String> headers = new HashMap<String, String>();
-
- /** Storage for body of HTTP response. */
- private final ByteArrayOutputStream body = new ByteArrayOutputStream(1024);
-
- private int responseCode = HTTP_STATUS_SUCCESS;
-
- public HttpResponseMessage() {
- // headers.put("Server", "HttpServer (" + Server.VERSION_STRING + ')');
- headers.put("Server", "HttpServer (" + "Mina 2.0" + ')');
- headers.put("Cache-Control", "private");
- headers.put("Content-Type", "text/html; charset=iso-8859-1");
- headers.put("Connection", "keep-alive");
- headers.put("Keep-Alive", "200");
- headers.put("Date", new SimpleDateFormat(
- "EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date()));
- headers.put("Last-Modified", new SimpleDateFormat(
- "EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date()));
- }
-
- public Map<String, String> getHeaders() {
- return headers;
- }
-
- public void setContentType(String contentType) {
- headers.put("Content-Type", contentType);
- }
-
- public void setResponseCode(int responseCode) {
- this.responseCode = responseCode;
- }
-
- public int getResponseCode() {
- return this.responseCode;
- }
-
- public void appendBody(byte[] b) {
- try {
- body.write(b);
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
-
- public void appendBody(String s) {
- try {
- body.write(s.getBytes());
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
-
- public IoBuffer getBody() {
- return IoBuffer.wrap(body.toByteArray());
- }
-
- public int getBodyLength() {
- return body.size();
- }
-
- }
3.封装HTTP的Server类及HTTP的Handler处理接口,其中HttpHandler接口是要暴露给外部就行自定义处理的。
- package com.ajita.httpserver;
-
- import java.io.IOException;
- import java.net.InetSocketAddress;
-
- import org.apache.mina.filter.codec.ProtocolCodecFilter;
- import org.apache.mina.filter.logging.LoggingFilter;
- import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
-
- public class HttpServer {
- /** Default HTTP port */
- private static final int DEFAULT_PORT = 8080;
- private NioSocketAcceptor acceptor;
- private boolean isRunning;
-
- private String encoding;
- private HttpHandler httpHandler;
-
- public String getEncoding() {
- return encoding;
- }
-
- public void setEncoding(String encoding) {
- this.encoding = encoding;
- HttpRequestDecoder.defaultEncoding = encoding;
- HttpResponseEncoder.defaultEncoding = encoding;
- }
-
- public HttpHandler getHttpHandler() {
- return httpHandler;
- }
-
- public void setHttpHandler(HttpHandler httpHandler) {
- this.httpHandler = httpHandler;
- }
-
- /**
- * 启动HTTP服务端箭筒HTTP请求
- *
- * @param port要监听的端口号
- * @throws IOException
- */
- public void run(int port) throws IOException {
- synchronized (this) {
- if (isRunning) {
- System.out.println("Server is already running.");
- return;
- }
- acceptor = new NioSocketAcceptor();
- acceptor.getFilterChain().addLast(
- "protocolFilter",
- new ProtocolCodecFilter(
- new HttpServerProtocolCodecFactory()));
- // acceptor.getFilterChain().addLast("logger", new LoggingFilter());
- ServerHandler handler = new ServerHandler();
- handler.setHandler(httpHandler);
- acceptor.setHandler(handler);
- acceptor.bind(new InetSocketAddress(port));
- isRunning = true;
- System.out.println("Server now listening on port " + port);
- }
- }
-
- /**
- * 使用默认端口8080
- *
- * @throws IOException
- */
- public void run() throws IOException {
- run(DEFAULT_PORT);
- }
-
- /**
- * 停止监听HTTP服务
- */
- public void stop() {
- synchronized (this) {
- if (!isRunning) {
- System.out.println("Server is already stoped.");
- return;
- }
- isRunning = false;
- try {
- acceptor.unbind();
- acceptor.dispose();
- System.out.println("Server is stoped.");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- public static void main(String[] args) {
- int port = DEFAULT_PORT;
-
- for (int i = 0; i < args.length; i++) {
- if (args[i].equals("-port")) {
- port = Integer.parseInt(args[i + 1]);
- }
- }
-
- try {
- // Create an acceptor
- NioSocketAcceptor acceptor = new NioSocketAcceptor();
-
- // Create a service configuration
- acceptor.getFilterChain().addLast(
- "protocolFilter",
- new ProtocolCodecFilter(
- new HttpServerProtocolCodecFactory()));
- acceptor.getFilterChain().addLast("logger", new LoggingFilter());
- acceptor.setHandler(new ServerHandler());
- acceptor.bind(new InetSocketAddress(port));
-
- System.out.println("Server now listening on port " + port);
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- }
- }
-
- package com.ajita.httpserver;
-
- import org.apache.mina.filter.codec.demux.DemuxingProtocolCodecFactory;
-
- public class HttpServerProtocolCodecFactory extends
- DemuxingProtocolCodecFactory {
- public HttpServerProtocolCodecFactory() {
- super.addMessageDecoder(HttpRequestDecoder.class);
- super.addMessageEncoder(HttpResponseMessage.class,
- HttpResponseEncoder.class);
- }
-
- }
-
- package com.ajita.httpserver;
-
- import org.apache.mina.core.future.IoFutureListener;
- import org.apache.mina.core.service.IoHandlerAdapter;
- import org.apache.mina.core.session.IdleStatus;
- import org.apache.mina.core.session.IoSession;
-
- public class ServerHandler extends IoHandlerAdapter {
- private HttpHandler handler;
-
- public HttpHandler getHandler() {
- return handler;
- }
-
- public void setHandler(HttpHandler handler) {
- this.handler = handler;
- }
-
- @Override
- public void sessionOpened(IoSession session) {
- // set idle time to 60 seconds
- session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 60);
- }
-
- @Override
- public void messageReceived(IoSession session, Object message) {
- // Check that we can service the request context
- HttpRequestMessage request = (HttpRequestMessage) message;
- HttpResponseMessage response = handler.handle(request);
- // HttpResponseMessage response = new HttpResponseMessage();
- // response.setContentType("text/plain");
- // response.setResponseCode(HttpResponseMessage.HTTP_STATUS_SUCCESS);
- // response.appendBody("CONNECTED");
-
- // msg.setResponseCode(HttpResponseMessage.HTTP_STATUS_SUCCESS);
- // byte[] b = new byte[ta.buffer.limit()];
- // ta.buffer.rewind().get(b);
- // msg.appendBody(b);
- // System.out.println("####################");
- // System.out.println(" GET_TILE RESPONSE SENT - ATTACHMENT GOOD DIAMOND.SI="+d.si+
- // ", "+new
- // java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss.SSS").format(new
- // java.util.Date()));
- // System.out.println("#################### - status="+ta.state+", index="+message.getIndex());
-
- // // Unknown request
- // response = new HttpResponseMessage();
- // response.setResponseCode(HttpResponseMessage.HTTP_STATUS_NOT_FOUND);
- // response.appendBody(String.format(
- // "<html><body><h1>UNKNOWN REQUEST %d</h1></body></html>",
- // HttpResponseMessage.HTTP_STATUS_NOT_FOUND));
-
- if (response != null) {
- session.write(response).addListener(IoFutureListener.CLOSE);
- }
- }
-
- @Override
- public void sessionIdle(IoSession session, IdleStatus status) {
- session.close(false);
- }
-
- @Override
- public void exceptionCaught(IoSession session, Throwable cause) {
- session.close(false);
- }
- }
-
- package com.ajita.httpserver;
-
- /**
- * HTTP请求的处理接口
- *
- * @author Ajita
- *
- */
- public interface HttpHandler {
- /**
- * 自定义HTTP请求处理需要实现的方法
- * @param request 一个HTTP请求对象
- * @return HTTP请求处理后的返回结果
- */
- HttpResponseMessage handle(HttpRequestMessage request);
- }
4.HTTP Client端,网上一抓一大把,就不说了
5.测试
建立测试类如下
- package com.jita;
-
- import java.io.IOException;
-
- import com.ajita.httpserver.HttpHandler;
- import com.ajita.httpserver.HttpRequestMessage;
- import com.ajita.httpserver.HttpResponseMessage;
- import com.ajita.httpserver.HttpServer;
-
- public class TestHttpServer {
- public static void main(String[] args) throws IOException,
- InterruptedException {
- HttpServer server = new HttpServer();
- server.setEncoding("GB2312");
- server.setHttpHandler(new HttpHandler() {
- public HttpResponseMessage handle(HttpRequestMessage request) {
- String level = request.getParameter("level");
- System.out.println(request.getParameter("level"));
- System.out.println(request.getContext());
- HttpResponseMessage response = new HttpResponseMessage();
- response.setContentType("text/plain");
- response.setResponseCode(HttpResponseMessage.HTTP_STATUS_SUCCESS);
- response.appendBody("CONNECTED\n");
- response.appendBody(level);
- return response;
- }
- });
- server.run();
-
- //Thread.sleep(10000);
- // server.stop();
- }
- }
启动,在浏览器中输入HTTP请求如:http://192.168.13.242:8080/test.do?level=aaa
附件是完整的代码。
|