zoukankan      html  css  js  c++  java
  • 基于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(), ',')  
                        + "
    ");  
            }  
            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
    ");  
                    response.appendBody(level);  
                    return response;  
                }  
            });  
            server.run();  
      
            //Thread.sleep(10000);  
            // server.stop();  
        }  
    }  

    启动,在浏览器中输入HTTP请求如:http://192.168.1.111:8080/test.do?level=1

  • 相关阅读:
    centos中文乱码修改字符编码使用centos支持中文
    java知识总结-26
    java知识总结-25
    java知识总结-24
    java知识总结-23
    java知识总结-22
    java知识总结-21
    java知识总结-20
    java知识总结-19
    java知识总结-18
  • 原文地址:https://www.cnblogs.com/xingchong/p/11492244.html
Copyright © 2011-2022 走看看