建立一個簡單的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。
服務端程式碼如下:
模擬的Request主要程式碼如下: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(); } } }
模擬的Response主要程式碼如下: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(); } }
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