1. 程式人生 > >建立一個簡單的web伺服器

建立一個簡單的web伺服器

Web伺服器也稱為超文字傳輸協議(HTTP)伺服器,因為它使用HTTP來跟客戶端進行通訊的。既然說到通訊那就離不了Java裡的兩個重要的類java.net.Socket和java.net.ServerSocket。這裡瀏覽器一方可以認為是一個客戶端,接收HTTP請求的一方可以認為是服務端。在這之前我們先說一下HTTP協議。

HTTP協議允許web伺服器和瀏覽器通過網路來進行傳送和接收資料。它是一種請求和響應協議。客戶端傳送一個請求,服務端響應這個請求。HTTP請求由三部分組成,分別是:請求行、訊息報頭(請求頭)、請求正文(一般是POST請求)。注意:請求頭和請求正文中間有一個空行。一個請求行以一個方法開頭,一個空格隔開,後面跟著請求的URI和協議的版本。格式如下Method Request-URI HTTP-Version CRLF。其中Method表示請求方法,Request-URI是一個統一資源識別符號,HTTP-Version表示請求的HTTP協議版本,CRLF表示回車和換行(除了作為結尾的CRLF之外,不允許出現單獨的CR或LF字元)HTTP響應也是由三個部分組成,分別是:狀態行、訊息報頭(響應頭)、響應正文。響應頭和響應正文中間有一個空行。狀態行的格式如下:HTTP-Version Status-Code Reason-Phrase CRLF。其中,HTTP-Version表示伺服器HTTP協議的版本,Status-Code表示伺服器發回的響應狀態碼,Reason-phrase表示狀態程式碼的文字描述。說了這麼多可能你看的還是有點暈,通過下面這張圖你應該能看得更清楚一點:


OK,我們上面說了瀏覽器可以當做是一個客戶端,那麼我們只需要寫一個服務端就行了。下面的程式碼只是簡單的模擬了請求靜態資源和處理一個簡單的Servlet。

服務端程式碼如下:

package com.zkn.imitate.tomcat.secondchapter.first;

import com.zkn.imitate.tomcat.secondchapter.Request;
import com.zkn.imitate.tomcat.secondchapter.Response;
import com.zkn.imitate.tomcat.secondchapter.StaticResourceProcessor;
import com.zkn.imitate.tomcat.utils.Constants;
import com.zkn.imitate.tomcat.utils.StringUtil;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by wb-zhangkenan on 2016/12/29.
 */
public class HttpServer {

    public static void main(String[] args){

        await();
    }

    private static void await() {

        ServerSocket serverSocket = null;
        try {
            boolean shutDown = false;
            //建立一個服務端
            serverSocket = new ServerSocket(8004,1, InetAddress.getByName("127.0.0.1"));
            while (!shutDown){
                //接收客戶端請求
                Socket socket = serverSocket.accept();
                Request request = new Request(socket.getInputStream());
                request.parseRequest();//解析請求資訊
                Response response = new Response(socket.getOutputStream());
                String uri = request.getUri();
                if(uri !=null && uri.startsWith("/favicon.ico")){

                }else if(!StringUtil.isEmpty(uri) && uri.startsWith("/static/")){
                    StaticResourceProcessor resouce = new StaticResourceProcessor();
                    resouce.process(request,response);//處理靜態資源
                }else{
                    ServletProcessor servletProcessor = new ServletProcessor();
                    servletProcessor.process(request,response);//處理Servlet
                }
                socket.close();
                shutDown = Constants.SHUT_DOWN.equals(request.getUri());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
模擬的Request主要程式碼如下:
public class Request implements ServletRequest {
    /**
     * 輸入流
     */
    private InputStream inputStream;
    /**
     * uri
     */
    private String uri;

    public Request(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public void parseRequest(){

        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        StringBuffer sb = new StringBuffer();
        String str = null;
        try {
            while((str = br.readLine()) != null){
                if("".equals(str))
                    break;
                sb.append(str).append("\n");
            }
            System.out.println(sb.toString());
            uri = StringUtil.parserUri(sb.toString()," ");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
模擬的Response主要程式碼如下:
public class Response implements ServletResponse {
    /**
     * 輸出流
     */
    private OutputStream outputStream;
    /**
     * 字元輸出流
     */
    private PrintWriter printWriter;

    public Response(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public void sendStaticResource(String path) {

        FileInputStream fis = null;
        try {
            File file = new File(Constants.WEB_PATH, path);
            if (file.exists() && !file.isDirectory()) {
                if (file.canRead()) {
                    fis = new FileInputStream(file);
                    int flag = 0;
                    byte[] bytes = new byte[1024];
                    while ((flag = fis.read(bytes)) != -1){
                        outputStream.write(bytes);
                    }
                }
            }else{
                PrintWriter printWriter = getWriter();
                //這裡用PrintWriter字元輸出流,設定自動重新整理
                printWriter.write("HTTP/1.1 404 File Not Found \r\n");
                printWriter.write("Content-Type: text/html\r\n");
                printWriter.write("Content-Length: 23\r\n");
                printWriter.write("\r\n");
                printWriter.write("<h1>File Not Found</h1>");
                printWriter.close();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fis != null)
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
靜態資源處理類:
public class StaticResourceProcessor {

    public void process(Request request,Response response){

        response.sendStaticResource(request.getUri());
    }
}
Servlet處理類如下:
public class FirstServlet implements Servlet{

    public void init(ServletConfig servletConfig) throws ServletException {

    }

    public ServletConfig getServletConfig() {
        return null;
    }

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {

        PrintWriter out = servletResponse.getWriter();
        out.println("Hello. Roses are red.");
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {

    }
}

ServletProcessor的程式碼如下:

package com.zkn.imitate.tomcat.secondchapter.first;

import com.zkn.imitate.tomcat.secondchapter.Request;
import com.zkn.imitate.tomcat.secondchapter.Response;
import com.zkn.imitate.tomcat.utils.Constants;
import com.zkn.imitate.tomcat.utils.StringUtil;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;

/**
 * Created by wb-zhangkenan on 2016/12/29.
 */
public class ServletProcessor {
    /**
     * 處理請求資訊
     * @param request
     * @param response
     */
    public void process(Request request, Response response){

        String str = request.getUri();
        String servletName = null;
        if(!StringUtil.isEmpty(str) && str.lastIndexOf("/") >= 0){
            servletName = str.substring(str.lastIndexOf("/")+1);
        }
        URLClassLoader classLoader = null;
        URL[] url = new URL[1];

        URLStreamHandler streamHandler = null;
        File classPath = new File(Constants.WEB_ROOT);

        try {
            //建立倉庫
            String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
            url[0] = new URL(null,repository,streamHandler);
            classLoader = new URLClassLoader(url);//URL類載入器

            Class clazz = classLoader.loadClass(servletName);
            Servlet servlet = (Servlet) clazz.newInstance();
            servlet.service(request,response);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }
}
下面我們看一下請求結果如何:

當我們輸入:http://localhost:8004/FirstServlet時,請求結果如下所示:

當我們輸入:http://localhost:8004/static/image01.jpg時,請求結果如下所示:


從上面的結果可以看到,我們做的簡單的web伺服器可以正常工作了。當然了這個只是最簡單的web伺服器了,以後我會把這個寫的完整一些強大一些。完整的程式碼請從這裡下載:https://github.com/zhangconan/ImitateTomCat