目的:
Java平臺(tái)下的內(nèi)部組件之間的通信。
1.WebService 由于感覺本身Java平臺(tái)下的Web Service標(biāo)準(zhǔn)就不夠統(tǒng)一,相互之間的調(diào)用就會(huì)有一些問題,更不用說與.net等其他平臺(tái)了。而且WebService也是對(duì)HTTP請(qǐng)求的一次封裝,效率上肯定會(huì)有損失,所以就不考慮用WebService了。
2.Socket,包括Java原生的Socket API和nio,本身都很好,效率也會(huì)不錯(cuò),它們之間的區(qū)別大概就是資源占用上了。但是使用Socket的通信,有幾個(gè)比較復(fù)雜的地方是:1)協(xié)議解析,要訂協(xié)議,解析及序列化2)粘包分包的處理(這個(gè)在長(zhǎng)連接的情況下才會(huì)出現(xiàn),可以不在考慮范圍內(nèi))3)資源的管理,弄不好的話會(huì)導(dǎo)致CPU占用較高或者內(nèi)存不知不覺泄露。
3.HTTP通信。由于應(yīng)用是獨(dú)立的,不能依托于Web容器。Java原生的HttpServer API好像不推薦使用(藏在好深的一個(gè)包里com.sun.net.httpserver.*)。
4.話說Mina的效率很高,是基于nio的異步通信,封裝簡(jiǎn)化了好多。通過比較簡(jiǎn)單的包裝就可以組成一個(gè)HTTP Server(下面例子中就是按照Mina官方提供的demo,自己改動(dòng)了幾點(diǎn)形成的)。然后HTTP的Client端也隨便封裝下就是了。
步驟
1.封裝HTTP請(qǐng)求消息類和響應(yīng)消息類
- package com.ajita.httpserver;
-
- import java.util.Map;
- import java.util.Map.Entry;
-
- /**
- * 使用Mina解析出的HTTP請(qǐng)求對(duì)象
- *
- * @author Ajita
- *
- */
- public class HttpRequestMessage {
- /**
- * HTTP請(qǐng)求的主要屬性及內(nèi)容
- */
- private Map<String, String[]> headers = null;
-
- public Map<String, String[]> getHeaders() {
- return headers;
- }
-
- public void setHeaders(Map<String, String[]> headers) {
- this.headers = headers;
- }
-
- /**
- * 獲取HTTP請(qǐng)求的Context信息
- */
- public String getContext() {
- String[] context = headers.get("Context");
- return context == null ? "" : context[0];
- }
-
- /**
- * 根據(jù)屬性名稱獲得屬性值數(shù)組第一個(gè)值,用于在url中傳遞的參數(shù)
- */
- public String getParameter(String name) {
- String[] param = headers.get("@".concat(name));
- return param == null ? "" : param[0];
- }
-
- /**
- * 根據(jù)屬性名稱獲得屬性值,用于在url中傳遞的參數(shù)
- */
- public String[] getParameters(String name) {
- String[] param = headers.get("@".concat(name));
- return param == null ? new String[] {} : param;
- }
-
- /**
- * 根據(jù)屬性名稱獲得屬性值,用于請(qǐng)求的特征參數(shù)
- */
- 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();
- }
-
- /**
- * 靜態(tài)方法,用來把一個(gè)字符串?dāng)?shù)組拼接成一個(gè)字符串
- *
- * @param s要拼接的字符串?dāng)?shù)組
- * @param sep數(shù)據(jù)元素之間的煩惱歌負(fù)
- * @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請(qǐng)求和發(fā)送HTTP響應(yīng)的編碼類和解碼類
- 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請(qǐng)求是否完整,若格式有錯(cuò)誤直接拋出異常
- */
- 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;
- }
-
- /**
- * 啟動(dòng)HTTP服務(wù)端箭筒HTTP請(qǐng)求
- *
- * @param port要監(jiān)聽的端口號(hào)
- * @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);
- }
- }
-
- /**
- * 使用默認(rèn)端口8080
- *
- * @throws IOException
- */
- public void run() throws IOException {
- run(DEFAULT_PORT);
- }
-
- /**
- * 停止監(jiān)聽HTTP服務(wù)
- */
- 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請(qǐng)求的處理接口
- *
- * @author Ajita
- *
- */
- public interface HttpHandler {
- /**
- * 自定義HTTP請(qǐng)求處理需要實(shí)現(xiàn)的方法
- * @param request 一個(gè)HTTP請(qǐng)求對(duì)象
- * @return HTTP請(qǐng)求處理后的返回結(jié)果
- */
- HttpResponseMessage handle(HttpRequestMessage request);
- }
4.HTTP Client端,網(wǎng)上一抓一大把,就不說了
5.測(cè)試
建立測(cè)試類如下
- 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();
- }
- }
啟動(dòng),在瀏覽器中輸入HTTP請(qǐng)求如:http://192.168.13.242:8080/test.do?level=aaa
附件是完整的代碼。
|