仿寫之前,我們要搞清楚都要用到哪些技術
- 自定義注解,比如Tomcat使用的是@Servlet,我們可以定義一個自己的@MyServlet
- 構造請求體和返回體,比如tomcat使用HttpRequest,我們可以自己定義myHttpRequest
- java去遍歷一個指定目錄,然后獲取到.java文件,再獲取到帶有@MyServlet注解的類
- 然后將這個注解里的path和這個類本身映射成map
- 通過反射去調用該類的方法(doGet、doPost)
- 還需要用到socket來監聽消息,并且對監聽到的消息進行處理
第一步:自定義注解
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface MyServlet {String path() default "";
}
第二步:定義HttpRequest以及HttpResponse、
public class MyHttpRequest {//定義一個map,用來存放請求體中的參數,key是參數名稱,value是參數值public Map<String,String> map = new HashMap<>();public String getParameter(String key){return map.get(key);}
}
public class MyHttpResponse {public OutputStream outputStream;public static final String responsebody = "HTTP/1.1 200+\r\n" + "Content-Type:text/html+\r\n"+ "\r\n";public MyHttpResponse(OutputStream outputStream) {this.outputStream = outputStream;}
}
第三步:遍歷整個目錄,把Java文件放入list中
private static void func(File file){File[] files = file.listFiles();String s;for (File file1 : files) {if (file1.isDirectory()){func(file1);}if (file1.isFile()){//取src之后的名字s = file1.toString().split("src")[1];//去掉src后邊的第一個\,得到全類名s = s.substring(1);//判斷是不是以.java結尾的文件if (s.length() >=5 && s.substring(s.length() - 5).equals(".java")){//把全類名中的\替換成.s = s.replace('\\','.');//去掉后綴名.javas = s.substring(0,s.length()-5);//把類名加入到list中javaclasses.add(s);}}}}
第四步:找出帶有Servlet注解的Java文件,并把注解中的path,類對象放入到map中
public static void getServlet() throws ClassNotFoundException {for (int i = 0; i < javaclasses.size(); i++) {String path = javaclasses.get(i);Class<?> cl = Class.forName(path);if (cl.isAnnotationPresent(MyServlet.class)){servletMap.put(cl.getAnnotation(MyServlet.class).path(),cl);}}}
第五步:創建socket連接
InetAddress localHost = InetAddress.getLocalHost();System.out.println("localhost" + localHost);ServerSocket serverSocket = new ServerSocket(8080, 10, localHost);System.out.println("等待建立連接");Socket server = serverSocket.accept();System.out.println("連接已建立");
第六步:定義線程接收報文
HttpAcceptThread httpAcceptThread = new HttpAcceptThread(server);Thread accept = new Thread(httpAcceptThread);accept.start();accept.join();
HttpAcceptThread類內容如下:
class HttpAcceptThread implements Runnable{private Socket socket;ArrayList<String> strings = new ArrayList<>();public HttpAcceptThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {System.out.println("開始接收http");try {BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));String s;while ((s = reader.readLine()).length() != 0){try {strings.add(s);System.out.println(s);} catch (Exception e){System.out.println("接收Http進程結束");break;}}System.out.println("接收http進程結束");} catch (IOException e) {e.printStackTrace();}}
}
第七步:處理httprequest,也就是通過反射去調用doGet和doPost方法
這一步有些復雜,尤其是對url切割時,但我給每一步都加了注釋,方便理解
? ? ? ? ? ? ?GET /address1?a=111&b=222
private static void requestHttp(Socket socket,String http) throws IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {//GET /address1?a=111&b=222(拿獲取到的這個url舉例)//先通過空格判斷是GET還是POSTString requestStyle = http.split(" ")[0];if (requestStyle.equals("GET")){//如果是GET,取空格后面部分,即/address1?a=111&b=222String httpPathAndParameter = http.split(" ")[1];//定義httpPathString httpPath;//創建httpRequest對象MyHttpRequest myHttpRequest = new MyHttpRequest();//通過索引位置判斷url里邊有沒有帶?if (httpPathAndParameter.indexOf("?") != -1){//如果有,由于有個/,因此我們要先拿到address1?a=111&b=222這部分httpPath = httpPathAndParameter.substring(1);//獲取問號前面部分,即address1,\\作為轉義字符使用httpPath = httpPath.split("\\?")[0];System.out.println(httpPath);//獲取問號后面部分的所有參數String parameterString = httpPathAndParameter.split("\\?")[1];//使用&分開String[] parameters = parameterString.split("&");for (int i = 0; i < parameters.length; i++) {//把參數及其值仿佛request的map中myHttpRequest.map.put(parameters[i].split("=")[0],parameters[i].split("=")[1]);}} else {//如果不存在?,也就說明不存在參數,我們只需要獲取httpPathhttpPath = httpPathAndParameter.substring(1);System.out.println(httpPath);}//創建HttpResponse對象OutputStream outputStream = socket.getOutputStream();MyHttpResponse myHttpResponse = new MyHttpResponse(outputStream);//反射調用doGetClass servletClass = servletMap.get(httpPath);Method doGet = servletClass.getMethod("doGet", MyHttpRequest.class, MyHttpResponse.class);doGet.invoke(servletClass.newInstance(),myHttpRequest,myHttpResponse);} else {//如果不是Get請求,也按照同樣的步驟,先取出/address1String httpPath = http.split(" ")[1];//去掉/,只留下address1httpPath = httpPath.substring(1);System.out.println(httpPath);MyHttpRequest myHttpRequest = new MyHttpRequest();OutputStream outputStream = socket.getOutputStream();MyHttpResponse myHttpResponse = new MyHttpResponse(outputStream);//根據httpPath取出類信息Class servletClass = servletMap.get(httpPath);//獲取doPost方法Method doPost = servletClass.getMethod("doPost", MyHttpRequest.class, MyHttpResponse.class);//調用doPost方法doPost.invoke(servletClass.newInstance(),myHttpRequest,myHttpResponse);}}
最后一步:把上面這些方法整合起來,在主方法中調用,同時定義好全局變量
public class MyTomcat {//用于存放Java類的全類名public static ArrayList<String> javaclasses = new ArrayList<>();//用于存放Servlet的類對象,其中key是Servlet的url,value是servlet的類對象public static HashMap<String,Class> servletMap = new HashMap<>();public static void main(String[] args) throws ClassNotFoundException, IOException, InterruptedException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {String inputPath = "D:\\JavaProject\\practice\\src\\tomcat";File file = new File(inputPath);//獲取.java后綴文件,并獲取全類名func(file);System.out.println(javaclasses);//獲取帶有servlet注解的類對象,并放到map中。getServlet();System.out.println(servletMap);InetAddress localHost = InetAddress.getLocalHost();System.out.println("localhost" + localHost);ServerSocket serverSocket = new ServerSocket(8080, 10, localHost);System.out.println("等待建立連接");Socket server = serverSocket.accept();System.out.println("連接已建立");//定義線程接收http報文HttpAcceptThread httpAcceptThread = new HttpAcceptThread(server);Thread accept = new Thread(httpAcceptThread);accept.start();accept.join();//處理請求requestHttp(server,httpAcceptThread.strings.get(0));}
然后就可以進行測試了,在測試類上方加上我們已經定義好的@MyServlet注解
@MyServlet(path = "address1")
public class Servlet1 {public void doGet(MyHttpRequest request, MyHttpResponse response) throws IOException {System.out.println("address1 GET響應:");System.out.println("a=" + request.getParameter("a"));System.out.println("\n響應的http如下:");String resp = MyHttpResponse.responsebody + "<!DOCTYPE html>\n" +"<html>\n" +"<head>\n" +" <meta charset=\"utf-8\" />\n" +"</head>\n" +"<body>\n" +" \n" +" <form name=\"my_form\" method=\"POST\">\n" +" <input type=\"button\" value=\"按下\" onclick=\"alert('你按下了按鈕')\">\n" +" </form>\n" +" \n" +"</body>\n" +"</html>";System.out.println(resp);response.outputStream.write(resp.getBytes());response.outputStream.flush();response.outputStream.close();}public void doPost(MyHttpRequest request, MyHttpResponse response) throws IOException {System.out.println("\n響應的http如下:");String resp = MyHttpResponse.responsebody +"{\"sorry\":\"we only respond to method GET now\"},\r\n" +"";System.out.println(resp);response.outputStream.write(resp.getBytes());response.outputStream.flush();response.outputStream.close();}
}
然后啟動項目
?可以看到本機ip地址,然后通過瀏覽器地址欄訪問
?這樣就實現了一個簡單的tomcat