java 手寫服務器

html

<html>
    <head>
        <title>表單</title>
    </head>
    <body>
        <form method="post" action="http://localhost:8888">
            <!--id常用在js中,用於前端區分唯一性;name常用在後端區分唯一性-->
            用戶名:<input type="text" name="username" id="username"/>
            密碼:<input type="password" name="password" id="password"/>
            <input type="submit" value="登錄">
        </form>
    </body>
</html>

http協議

HTTP請求頭和響應頭含義

1、請求(客戶端->服務端[request])
    GET(請求的方式) /books/java.html(請求的目標資源) HTTP/1.1(請求採用的協議和版本號)
    Accept: */*(客戶端能接收的資源類型)
    Accept-Language: en-us(客戶端接收的語言類型)
    Connection: Keep-Alive(維護客戶端和服務端的連接關係)
    Host: localhost:8080(連接的目標主機和端口號)
    Referer: http://localhost/links.asp(從來於哪裏)
    User-Agent: Mozilla/4.0(客戶端版本號的名字)
    Accept-Encoding: gzip, deflate(客戶端能接收的壓縮數據的類型)
    If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT(緩存時間) 
    Cookie(客戶端暫存服務端的信息)
    Date: Tue, 11 Jul 2000 18:23:51 GMT(客戶端請求服務端的時間)

2、響應(服務端->客戶端[response])
    HTTP/1.1(響應採用的協議和版本號) 200(狀態碼) OK(描述信息)
    302(客戶端請求服務端,但服務端沒有對應的資源,服務端要客戶端再次請求找其它的服務端,即客戶端二次請求,重定向) 
    307(客戶端請求服務端,但服務端沒有對應的資源,服務端自行再次請求找其它的服務端,即客戶端一次請求,轉發)
    304(客戶端請求服務端,此時客戶端緩存中有,無需再從服務端下載新的內容,服務端叫客戶端自行找緩存,優化)
    500(客戶端請求的資源,服務端存在,但在執行時出錯)
    Location: http://www.baidu.com(服務端需要客戶端訪問的頁面路徑) 
    Server:apache tomcat(服務端的Web服務端名)
    Content-Encoding: gzip(服務端能夠發送壓縮編碼類型) 
    Content-Length: 80(服務端發送的壓縮數據的長度) 
    Content-Language: zh-cn(服務端發送的語言類型) 
    Content-Type: text/html; charset=GB2312(服務端發送的類型及採用的編碼方式)
    Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT(服務端對該資源最後修改的時間)
    Refresh: 1;url=http://www.it315.org(服務端要求客戶端1秒鐘後,刷新,然後訪問指定的頁面路徑)
    Content-Disposition: attachment; filename=aaa.zip(服務端要求客戶端以下載文件的方式打開該文件)
    Transfer-Encoding: chunked(分塊傳遞數據到客戶端)  
    Set-Cookie:SS=Q0=5Lb_nQ; path=/search(服務端發送到客戶端的暫存數據)
    Expires: -1//3種(服務端禁止客戶端緩存頁面數據)
    Cache-Control: no-cache(服務端禁止客戶端緩存頁面數據)  
    Pragma: no-cache(服務端禁止客戶端緩存頁面數據)   
    Connection: close(1.0)/(1.1)Keep-Alive(維護客戶端和服務端的連接關係)  
    Date: Tue, 11 Jul 2000 18:23:51 GMT(服務端響應客戶端的時間)

httpserver

項目結構

/**
 * 創建服務器,並啓動 
 * 1、請求 
 * 2、響應
 * @author L J
 */
public class Server {
    private ServerSocket server;
    //是否停止服務
    private boolean isShutDown;

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 啓動服務器方法
     */
    public void start() {
        start(8888);
    }

    /**
     * 啓動服務器方法
     */
    public void start(int port) {
        try {
            server = new ServerSocket(port);
            this.receive();
        } catch (IOException e) {
            stop();
        }
    }

    /**
     * 接收客戶端
     */
    private void receive() {
        try {
            while(!isShutDown) {
                new Thread(new Dispatcher(server.accept())).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 停止服務器方法
     */
    public void stop() {
        isShutDown = true;
        CloseUtil.close(server);
    }
}
/**
 * 封裝Request 
 * @author L J
 */
public class Request {
    //請求方式
    private String method;
    //請求資源
    private String url;
    //請求參數
    private Map<String, List<String>> parameterValue;
    //換行符常量
    public static final String CRLF = "\r\n";
    //輸入流
    private InputStream is;
    //請求信息
    private String requestInfo;

    public Request() {
        method = "";
        url = "";
        parameterValue = new HashMap<String, List<String>>();
    }

    public Request(InputStream is) {
        this();
        this.is = is;
        try {
            byte[] data = new byte[204800];
            int len = is.read(data);
            requestInfo = new String(data, 0, len);
        } catch (IOException e) {
            return;
        }
        //分析請求頭信息
        parseRequestInfo();
    }

    /**
     * 分析請求頭信息
     */
    private void parseRequestInfo() {
        if(requestInfo == null || (requestInfo = requestInfo.trim()).equals("")) {
            return;
        }

        /**
         * 思路
         * =======================================
         * 從頭信息中分析出:請求方式  請求路徑  請求參數(可能不存在)
         * 如果是POST請求方式,請求參數可能在最後的正文中
         * =======================================
         */

        //接收請求參數
        String paramString = "";

        //獲取請求方式
        String firstLine = requestInfo.substring(0, requestInfo.indexOf(CRLF));
        int index = requestInfo.indexOf("/"); //  /的位置
        this.method = firstLine.substring(0, index).trim();
        String urlStr = firstLine.substring(index, firstLine.indexOf("HTTP/1.1")).trim();
        //只考慮get和post請求方式
        if(this.method.equalsIgnoreCase("post")) {//post
            this.url = urlStr;
            //參數在正文中
            paramString = requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();
        }else if(this.method.equalsIgnoreCase("get")) {//get
            if(urlStr.contains("?")) { //如果有參數
                String[] urlArray = urlStr.split("\\?");
                this.url = urlArray[0]; //路徑
                paramString = urlArray[1];  //參數字符串
            }else{
                this.url = urlStr;
            }
        }

        //不存在請求參數
        if(paramString.equals("")) {
            return;
        }

        //將請求參數封裝到Map中
        parseParams(paramString);
    }

    /**
     * 解析參數,封裝到Map中
     * @param paramString
     */
    private void parseParams(String paramString) {
        //分割請求參數字符串
        StringTokenizer token = new StringTokenizer(paramString, "&");
        while(token.hasMoreTokens()) {
            String keyValue = token.nextToken();
            String[] keyValues = keyValue.split("=");

            //如果有參數沒有值,將值設爲null
            if(keyValues.length == 1){
                keyValues = Arrays.copyOf(keyValues, 2);
                keyValues[1] = null;
            }

            String key = keyValues[0].trim();
            String value = keyValues[1] == null ? null : decode(keyValues[1].trim(), "utf-8");

            //轉換成Map 分揀存儲
            if(!parameterValue.containsKey(key)) {
                parameterValue.put(key, new ArrayList<String>());
            }
            List<String> values = parameterValue.get(key);
            values.add(value);
        } 
    }

    /**
     * 根據頁面的name獲取對應的多個值
     * @param name
     * @return
     */
    public String[] getParameterValues(String name) {
        List<String> values = null;
        if((values=parameterValue.get(name)) == null) {
            return null;
        }else{
            return values.toArray(new String[0]);
        }
    }

    /**
     * 根據頁面的name獲取對應的一個值
     * @param name
     * @return
     */
    public String getParameter(String name) {
        String[] values = getParameterValues(name);
        if(values == null) {
            return null;
        }
        return values[0];
    }

    //解碼,解決中文亂碼問題
    private String decode(String value, String code) {
        try {
            return URLDecoder.decode(value, code);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getUrl() {
        return url;
    }
}
/**
 * 封裝響應信息
 * @author L J
 */
public class Response {
    //存儲響應頭信息
    private StringBuilder headInfo;
    //存儲響應正文信息
    private StringBuilder content;
    //輸出流
    private BufferedWriter bw;
    //換行符常量
    private static final String CRLF = "\r\n";
    //空格常量
    private static final String BLANK = " ";
    //存儲正文長度
    private int len = 0;

    public Response() {
        headInfo = new StringBuilder();
        content = new StringBuilder();
        len = 0;
    }

    public Response(OutputStream os) {
        this();
        bw = new BufferedWriter(new OutputStreamWriter(os));
    }

    public Response(Socket client) {
        this();
        try {
            bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
        } catch (IOException e) {
            headInfo = null;
        }
    }

    /**
     * 構建正文
     * @param info
     * @return
     */
    public Response print(String info) {
        content.append(info);
        len += info.getBytes().length;
        return this;
    }

    /**
     * 構建正文+回車
     * @param info
     * @return
     */
    public Response println(String info) {
        content.append(info).append(CRLF);
        len += (info+CRLF).getBytes().length;
        return this;
    }

    /**
     * 構建響應頭
     */
    private void createHeadInfo(int code) {
        // 1)HTTP協議版本、狀態碼、描述
        headInfo.append("HTTP/1.1").append(BLANK).append(code).append(BLANK);
        switch(code) {
            //狀態碼很多,不一一列舉
            case 200:
                //響應成功
                headInfo.append("OK");
                break;
            case 404:
                //找不到資源
                headInfo.append("NOT FOUND");
                break;
            case 500:
                //客戶端請求的資源,服務端存在,但在執行時出錯
                headInfo.append("SERVER ERROR");
                break;
        }
        headInfo.append(CRLF);

        // 2)響應頭
        headInfo.append("Server:bjsxt Server/0.0.1").append(CRLF);
        headInfo.append("Date:").append(new Date()).append(CRLF);
        headInfo.append("Content-type:text/html;charset=GBK").append(CRLF);
        // 正文長度:字節長度
        headInfo.append("Content-Length:").append(len).append(CRLF);
        // 3)正文之前
        headInfo.append(CRLF);
    }

    /**
     * 推送到客戶端
     * @throws IOException 
     */
    void pushToClient(int code) throws IOException {
        if(headInfo == null) {
            code = 500;
        }
        createHeadInfo(code);
        //頭信息+分隔符
        bw.append(headInfo.toString());
        //正文
        bw.append(content.toString());
        bw.flush();
        CloseUtil.close(bw);
    }
}
/**
 * 一個請求與一個響應  就一個此對象
 * @author L J
 */
public class Dispatcher implements Runnable{
    private Socket client;
    //請求
    private Request req;
    //響應
    private Response res;
    //狀態碼
    private int code;

    public Dispatcher(Socket client) {
        this.client = client;
        try {
            req = new Request(client.getInputStream());
            res = new Response(client.getOutputStream());
        } catch (IOException e) {
            code = 500;
            return;
        }
    }

    @Override
    public void run() {
        try {
            Servlet servlet = WebApp.getServlet(req.getUrl());
            if(servlet == null) {
                this.code = 404;
            }else{
                //調用服務
                servlet.service(req, res);
            }
            //將響應信息輸出到客戶端
            res.pushToClient(code);
        } catch (Exception e) {
            this.code = 500;
        }
        //關閉socket
        CloseUtil.close(client);
    }
}
/**
 * 上下文容器
 * @author L J
 */
public class ServletContext {
    //爲每一個Servlet取一個別名,一個Servlet可以有多條路徑進行訪問
    //login-->com.belief.server.LoginServlet
    private Map<String, String> servlet;
    //映射  /log-->login /login-->login
    private Map<String, String> mapping;

    public ServletContext() {
        servlet = new HashMap<String, String>();
        mapping = new HashMap<String, String>();
    }

    public Map<String, String> getServlet() {
        return servlet;
    }
    public void setServlet(Map<String, String> servlet) {
        this.servlet = servlet;
    }
    public Map<String, String> getMapping() {
        return mapping;
    }
    public void setMapping(Map<String, String> mapping) {
        this.mapping = mapping;
    }
}
/**
 * 存儲:
 * <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.belief.server.LoginServlet</servlet-class>
    </servlet>
 * @author L J
 */
public class Entity {
    //Servlet的別名
    private String name;
    //Servlet的路徑
    private String clz;

    public Entity() {
    }

    public Entity(String name, String clz) {
        this.name = name;
        this.clz = clz;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClz() {
        return clz;
    }

    public void setClz(String clz) {
        this.clz = clz;
    }
}
/**
 * 存儲:
 * <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
 * @author L J
 */
public class Mapping {
    //Servlet的別名
    private String name;
    //要訪問Servlet的url
    private List<String> urlPattern;

    public Mapping() {
        urlPattern = new ArrayList<String>();
    }

    public Mapping(String name, List<String> urlPattern) {
        this.name = name;
        this.urlPattern = urlPattern;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getUrlPattern() {
        return urlPattern;
    }

    public void setUrlPattern(List<String> urlPattern) {
        this.urlPattern = urlPattern;
    }
}
//SAX解析web.xml文件
public class WebHandler extends DefaultHandler{
    //存儲解析出來的Entity
    private List<Entity> entityList;
    //存儲解析出來的Mapping
    private List<Mapping> mappingList;
    //Entity實體--><servlet></servlet>標籤
    private Entity entity;
    //Mapping實體--><servlet-mapping></servlet-mapping>標籤
    private Mapping mapping;
    //記錄開始標籤
    private String beginTag;
    //是否是在解析servlet-mapping
    private boolean isMap;

    @Override
    public void startDocument() throws SAXException {
        //文檔解析開始
        entityList = new ArrayList<Entity>();
        mappingList = new ArrayList<Mapping>();
    }

    @Override
    public void startElement(String uri, String localName, String qName,
            Attributes attributes) throws SAXException {
        //開始元素
        if(qName != null) {
            beginTag = qName;
            if(qName.equals("servlet")) {
                isMap = false;
                entity = new Entity();
            }else if(qName.equals("servlet-mapping")) {
                isMap = true;
                mapping = new Mapping();
            }
        }
    }

    @Override
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        //處理內容
        if(beginTag != null) {
            String str = new String(ch, start, length);
            if(isMap) {
                if(beginTag.equals("servlet-name")) {
                    mapping.setName(str);
                }else if(beginTag.equals("url-pattern")) {
                    mapping.getUrlPattern().add(str);
                }
            }else{
                if(beginTag.equals("servlet-name")) {
                    entity.setName(str);
                }else if(beginTag.equals("servlet-class")) {
                    entity.setClz(str);
                }
            }
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName)
    throws SAXException {
        //結束元素
        if(qName != null) {
            if(qName.equals("servlet")) {
                entityList.add(entity);
            }else if(qName.equals("servlet-mapping")) {
                mappingList.add(mapping);
            }
        }
        beginTag = null;
    }

    @Override
    public void endDocument() throws SAXException {
        //文檔解析結束
    }

    public List<Entity> getEntityList() {
        return entityList;
    }

    public void setEntityList(List<Entity> entityList) {
        this.entityList = entityList;
    }

    public List<Mapping> getMappingList() {
        return mappingList;
    }

    public void setMappingList(List<Mapping> mappingList) {
        this.mappingList = mappingList;
    }
}
//模擬應用
public class WebApp {
    private static ServletContext context;
    static {
        try {
            //獲取解析工廠
            SAXParserFactory factory = SAXParserFactory.newInstance();
            //獲取解析器
            SAXParser sax = factory.newSAXParser();
            //指定xml+處理器
            WebHandler web = new WebHandler();
            sax.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("WEB-INF/web.xml"), web);

            context = new ServletContext();

            //將List轉成Map
            Map<String,String> mapp = context.getMapping();
            Map<String,String> servlet = context.getServlet();

            //servlet-name  servlet-class
            for(Entity entity : web.getEntityList()) {
                servlet.put(entity.getName(), entity.getClz());
            }

            //url-pattern  servlet-name
            for(Mapping mapping : web.getMappingList()) {
                List<String> urls = mapping.getUrlPattern();
                for(String url : urls) {
                    mapp.put(url, mapping.getName());
                }
            }
        } catch (Exception e) {

        }
    }

    public static Servlet getServlet(String url) throws Exception {
        //如果沒有url或url==null
        if(url == null && (url = url.trim()).equals("")) {
            return null;
        }

        //利用反射根據字符串(完整路徑)創建對象
        //return context.getServlet().get(context.getMapping().get(url));
        String name = context.getServlet().get(context.getMapping().get(url));
        return (Servlet)Class.forName(name).newInstance();
    }
}
/**
 * 關閉流工具類
 * 
 * @author L J
 */
public class CloseUtil {
    // 關閉流
    public static void close(Closeable... io) {
        for (Closeable temp : io) {
            if (temp != null) {
                try {
                    temp.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 關閉Socket
    public static void close(Socket socket) {
        try {
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //關閉ServerSocket
    public static void close(ServerSocket serverSocket) {
        try {
            if (serverSocket != null) {
                serverSocket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public abstract class Servlet {
    //服務端的處理
    public void service(Request req, Response res) throws Exception {
        this.doGet(req, res);
        this.doPost(req, res);
    }

    protected abstract void doGet(Request req, Response res) throws Exception;
    protected abstract void doPost(Request req, Response res) throws Exception;
}
//登錄
public class LoginServlet extends Servlet{
    @Override
    public void doGet(Request req, Response res) throws Exception {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        boolean flag = login(username, password);
        if(flag) {
            res.println("登錄成功!");
        }else{
            res.println("登錄失敗!");
        }
    }

    public boolean login(String username, String password) {
        return username.equals("哈哈") && password.equals("123456");
    }

    @Override
    public void doPost(Request req, Response res) throws Exception {

    }
}
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <!-- 登錄 -->
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.belief.servlet.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
        <url-pattern>/log</url-pattern>
    </servlet-mapping>
    <!-- 註冊 -->
    <servlet>
        <servlet-name>register</servlet-name>
        <servlet-class>com.belief.servlet.RegisterServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>register</servlet-name>
        <url-pattern>/register</url-pattern>
    </servlet-mapping>
</web-app>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章