基於Mina的Http Server以及簡單的Http請求客戶端


目的:
    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.1.111:8080/test.do?level=1

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM