• 手写SpringBoot底层代码(WebServer项目)


    1.WebServer项目

    1.1项目目录

    1.1.1Java目录

    1.1.2HTML目录

    1.2Java代码

    1.2.1Controller(注解)

    1. package com.webserver.annotations;
    2. import java.lang.annotation.ElementType;
    3. import java.lang.annotation.Retention;
    4. import java.lang.annotation.RetentionPolicy;
    5. import java.lang.annotation.Target;
    6. @Target(ElementType.TYPE)
    7. @Retention(RetentionPolicy.RUNTIME)
    8. public @interface Controller {
    9. }

    1.2.2RequestMapping(注解)

    1. package com.webserver.annotations;
    2. import java.lang.annotation.ElementType;
    3. import java.lang.annotation.Retention;
    4. import java.lang.annotation.RetentionPolicy;
    5. import java.lang.annotation.Target;
    6. @Target(ElementType.METHOD)
    7. @Retention(RetentionPolicy.RUNTIME)
    8. public @interface RequestMapping {
    9. String value();
    10. }

    1.2.3ArticleController(文章业务)

    1. package com.webserver.controller;
    2. import com.webserver.annotations.Controller;
    3. import com.webserver.annotations.RequestMapping;
    4. import com.webserver.core.ClientHandler;
    5. import com.webserver.entity.Article;
    6. import com.webserver.http.HttpServletRequest;
    7. import com.webserver.http.HttpServletResponse;
    8. import java.io.File;
    9. import java.io.FileOutputStream;
    10. import java.io.IOException;
    11. import java.io.ObjectOutputStream;
    12. import java.net.URISyntaxException;
    13. @Controller
    14. public class ArticleController {
    15. private static File rootDir;
    16. private static File staticDir;
    17. //表示articles目录
    18. private static File articleDir;
    19. static{
    20. try {
    21. rootDir = new File(
    22. ClientHandler.class.getClassLoader()
    23. .getResource(".").toURI()
    24. );
    25. staticDir = new File(rootDir,"static");
    26. } catch (URISyntaxException e) {
    27. e.printStackTrace();
    28. }
    29. articleDir = new File("articles");
    30. if(!articleDir.exists()){
    31. articleDir.mkdirs();
    32. }
    33. }
    34. @RequestMapping("/myweb/writeArticle")
    35. public void writeArticle(HttpServletRequest request, HttpServletResponse response){
    36. //1获取表单数据
    37. String title = request.getParameter("title");
    38. String author = request.getParameter("author");
    39. String content = request.getParameter("content");
    40. if(title==null||author==null||content==null){
    41. File file = new File(staticDir,"/myweb/article_info_error.html");
    42. response.setContentFile(file);
    43. return;
    44. }
    45. //2写入文件
    46. File articleFile = new File(articleDir,title+".obj");
    47. if(articleFile.exists()){//文件存在,说明重复的标题,不能发表(需求过于严苛,后期数据库可通过ID避免该问题).
    48. File file = new File(staticDir,"/myweb/have_article.html");
    49. response.setContentFile(file);
    50. return;
    51. }
    52. Article article = new Article(title,author,content);
    53. try (
    54. FileOutputStream fos = new FileOutputStream(articleFile);
    55. ObjectOutputStream oos = new ObjectOutputStream(fos);
    56. ){
    57. oos.writeObject(article);
    58. File file = new File(staticDir,"/myweb/article_success.html");
    59. response.setContentFile(file);
    60. } catch (IOException e) {
    61. e.printStackTrace();
    62. }
    63. //3响应页面
    64. }
    65. }

    1.2.4ToolsController(二维码,验证码业务)

    1. package com.webserver.controller;
    2. import com.webserver.annotations.Controller;
    3. import com.webserver.annotations.RequestMapping;
    4. import com.webserver.http.HttpServletRequest;
    5. import com.webserver.http.HttpServletResponse;
    6. import qrcode.QRCodeUtil;
    7. import javax.imageio.ImageIO;
    8. import java.awt.*;
    9. import java.awt.image.BufferedImage;
    10. import java.io.FileOutputStream;
    11. import java.io.IOException;
    12. import java.util.Random;
    13. @Controller
    14. public class ToolsController {
    15. @RequestMapping("/myweb/createQR")
    16. public void createQR(HttpServletRequest request, HttpServletResponse response){
    17. String line = request.getParameter("content");
    18. try {
    19. response.setContentType("image/jpeg");
    20. QRCodeUtil.encode(
    21. line, //二维码上显示的文字
    22. "./logo.jpg", //二维码中间的logo图
    23. response.getOutputStream(),//生成的二维码数据会通过该流写出
    24. true);//是否压缩logo图片的尺寸
    25. System.out.println("二维码已生成");
    26. } catch (Exception e) {
    27. e.printStackTrace();
    28. }
    29. }
    30. @RequestMapping("/myweb/random.jpg")
    31. public void createRandomImage(HttpServletRequest request,HttpServletResponse response){
    32. response.setContentType("image/jpeg");
    33. //1创建一张空白图片,同时指定宽高 理解为:搞一张白纸,准备画画
    34. BufferedImage image = new BufferedImage(
    35. 70,30,BufferedImage.TYPE_INT_RGB
    36. );
    37. //2通过图片获取绘制该图片的画笔,通过这个画笔就可以往该图片上绘制了
    38. Graphics g = image.getGraphics();
    39. Random random = new Random();
    40. //3随机生成个颜色涂满整张画布
    41. Color color = new Color(
    42. random.nextInt(256),
    43. random.nextInt(256),
    44. random.nextInt(256));
    45. g.setColor(color);//设置画笔颜色
    46. g.fillRect(0,0,70,30);
    47. //4向画布上绘制文字
    48. String line = "abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    49. g.setFont(new Font(null,Font.BOLD,20));
    50. for(int i=0;i<4;i++) {
    51. color = new Color(
    52. random.nextInt(256),
    53. random.nextInt(256),
    54. random.nextInt(256));
    55. g.setColor(color);//设置画笔颜色
    56. String str = line.charAt(random.nextInt(line.length())) + "";
    57. g.drawString(str, i*15+5, 18+ random.nextInt(11)-5);
    58. }
    59. //5随机生成5条干扰线
    60. for(int i=0;i<5;i++){
    61. color = new Color(
    62. random.nextInt(256),
    63. random.nextInt(256),
    64. random.nextInt(256));
    65. g.setColor(color);//设置画笔颜色
    66. g.drawLine(random.nextInt(71), random.nextInt(31),
    67. random.nextInt(71), random.nextInt(31));
    68. }
    69. try {
    70. ImageIO.write(image,"jpg",
    71. response.getOutputStream());
    72. System.out.println("图片已生成");
    73. } catch (IOException e) {
    74. e.printStackTrace();
    75. }
    76. }
    77. public static void main(String[] args) {
    78. //1创建一张空白图片,同时指定宽高 理解为:搞一张白纸,准备画画
    79. BufferedImage image = new BufferedImage(
    80. 70,30,BufferedImage.TYPE_INT_RGB
    81. );
    82. //2通过图片获取绘制该图片的画笔,通过这个画笔就可以往该图片上绘制了
    83. Graphics g = image.getGraphics();
    84. Random random = new Random();
    85. //3随机生成个颜色涂满整张画布
    86. Color color = new Color(
    87. random.nextInt(256),
    88. random.nextInt(256),
    89. random.nextInt(256));
    90. g.setColor(color);//设置画笔颜色
    91. g.fillRect(0,0,70,30);
    92. //4向画布上绘制文字
    93. String line = "abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    94. g.setFont(new Font(null,Font.BOLD,20));
    95. for(int i=0;i<4;i++) {
    96. color = new Color(
    97. random.nextInt(256),
    98. random.nextInt(256),
    99. random.nextInt(256));
    100. g.setColor(color);//设置画笔颜色
    101. String str = line.charAt(random.nextInt(line.length())) + "";
    102. g.drawString(str, i*15+5, 18+ random.nextInt(11)-5);
    103. }
    104. //5随机生成5条干扰线
    105. for(int i=0;i<5;i++){
    106. color = new Color(
    107. random.nextInt(256),
    108. random.nextInt(256),
    109. random.nextInt(256));
    110. g.setColor(color);//设置画笔颜色
    111. g.drawLine(random.nextInt(71), random.nextInt(31),
    112. random.nextInt(71), random.nextInt(31));
    113. }
    114. try {
    115. ImageIO.write(image,"jpg",
    116. new FileOutputStream("./random.jpg"));
    117. System.out.println("图片已生成");
    118. } catch (IOException e) {
    119. e.printStackTrace();
    120. }
    121. }
    122. // public static void main(String[] args) {
    123. // try {
    124. // String line = "扫你妹!";
    125. // //参数1:二维码上显示的文字,参数2:二维码图片生成的位置
    126. QRCodeUtil.encode(line,"./qr.jpg");
    127. //
    128. // //参数1:二维码上显示的文字,参数2:二维码中间的logo图,
    129. // //参数3:二维码生成的位置,参数4:是否压缩logo图片的尺寸
    130. QRCodeUtil.encode(line,"./logo.jpg",
    131. "./qr.jpg",true);
    132. //
    133. // QRCodeUtil.encode(
    134. // line, //二维码上显示的文字
    135. // "./logo.jpg", //二维码中间的logo图
    136. // new FileOutputStream("./qr.jpg"),//生成的二维码数据会通过该流写出
    137. // true);//是否压缩logo图片的尺寸
    138. //
    139. // System.out.println("二维码已生成");
    140. // } catch (Exception e) {
    141. // e.printStackTrace();
    142. // }
    143. // }
    144. }

    1.2.5UserController(用户业务)

    1. package com.webserver.controller;
    2. import com.webserver.annotations.Controller;
    3. import com.webserver.annotations.RequestMapping;
    4. import com.webserver.entity.User;
    5. import com.webserver.http.HttpServletRequest;
    6. import com.webserver.http.HttpServletResponse;
    7. import org.apache.log4j.Logger;
    8. import java.io.*;
    9. import java.util.ArrayList;
    10. import java.util.List;
    11. /**
    12. * 处理与用户相关的业务操作
    13. */
    14. @Controller
    15. public class UserController {
    16. private static Logger logger = Logger.getLogger(UserController.class);
    17. //表示users目录
    18. private static File userDir;
    19. static {
    20. userDir = new File("users");
    21. if (!userDir.exists()) {
    22. userDir.mkdirs();
    23. }
    24. }
    25. /**
    26. * 生成展示所有用户信息的动态页面
    27. *
    28. * @param request
    29. * @param response
    30. */
    31. @RequestMapping("/myweb/showAllUser")
    32. public void showAllUser(HttpServletRequest request, HttpServletResponse response) {
    33. /*
    34. 1:通过读取users目录下所有的obj文件,进行反序列化
    35. 得到所有的注册用户信息(若干的User对象)将他们存入一个List集合备用
    36. 2:拼接一个HTML页面,并将所有用户信息拼接到table表格中用于展示给用户
    37. 3:将拼接好的HTML代码作为正文给浏览器响应回去
    38. */
    39. //1
    40. List<User> userList = new ArrayList<>();
    41. /*
    42. 1.1:通过File的listFiles方法,从userDir目录下获取所有的obj文件
    43. 要使用文件过滤器
    44. 1.2:遍历每一个obj文件,并利用对象流与文件流的连接,将obj文件中的
    45. User对象反序列化出来
    46. 1.3:将反序列化的User对象存入userList集合中
    47. */
    48. //1.1
    49. File[] subs = userDir.listFiles(f -> f.getName().endsWith(".obj"));
    50. //1.2
    51. for (File userFile : subs) {
    52. try (
    53. FileInputStream fis = new FileInputStream(userFile);
    54. ObjectInputStream ois = new ObjectInputStream(fis);
    55. ) {
    56. User user = (User) ois.readObject();
    57. //1.3
    58. userList.add(user);
    59. } catch (IOException e) {
    60. e.printStackTrace();
    61. } catch (ClassNotFoundException e) {
    62. e.printStackTrace();
    63. }
    64. }
    65. System.out.println(userList);
    66. //2将数据拼接到html生成页面
    67. response.setContentType("text/html");
    68. PrintWriter pw = response.getWriter();
    69. pw.println("<!DOCTYPE html>");
    70. pw.println("<html lang=\"en\">");
    71. pw.println("<head>");
    72. pw.println("<meta charset=\"UTF-8\">");
    73. pw.println("<title>用户列表</title>");
    74. pw.println("</head>");
    75. pw.println("<body>");
    76. pw.println("<center>");
    77. pw.println("<h1>用户列表</h1>");
    78. pw.println("<table border=\"1\">");
    79. pw.println("<tr>");
    80. pw.println("<td>用户名</td>");
    81. pw.println("<td>密码</td>");
    82. pw.println("<td>昵称</td>");
    83. pw.println("<td>年龄</td>");
    84. pw.println("</tr>");
    85. for (User user : userList) {
    86. pw.println("<tr>");
    87. pw.println("<td>" + user.getUsername() + "</td>");
    88. pw.println("<td>" + user.getPassword() + "</td>");
    89. pw.println("<td>" + user.getNickname() + "</td>");
    90. pw.println("<td>" + user.getAge() + "</td>");
    91. pw.println("</tr>");
    92. }
    93. pw.println("</table>");
    94. pw.println("</center>");
    95. pw.println("</body>");
    96. pw.println("</html>");
    97. System.out.println("页面已生成");
    98. }
    99. @RequestMapping("/myweb/login")
    100. public void login(HttpServletRequest request, HttpServletResponse response) {
    101. //1获取用户输入的登录信息
    102. String username = request.getParameter("username");
    103. String password = request.getParameter("password");
    104. if (username == null || password == null) {
    105. response.sendRedirect("/myweb/login_info_error.html");
    106. return;
    107. }
    108. //2
    109. File userFile = new File(userDir, username + ".obj");
    110. if (userFile.exists()) {//用户名输入正确
    111. try (
    112. FileInputStream fis = new FileInputStream(userFile);
    113. ObjectInputStream ois = new ObjectInputStream(fis);
    114. ) {
    115. //读取该注册用户信息
    116. User user = (User) ois.readObject();
    117. if (user.getPassword().equals(password)) {//密码正确
    118. //登录成功
    119. response.sendRedirect("/myweb/login_success.html");
    120. return;
    121. }
    122. } catch (IOException | ClassNotFoundException e) {
    123. e.printStackTrace();
    124. }
    125. }
    126. //如果程序走到这里,情况1:用户名没有输入正确,文件不存在
    127. // 情况2:用户名对了,但是密码不对
    128. response.sendRedirect("/myweb/login_fail.html");
    129. }
    130. @RequestMapping("/myweb/reg")
    131. public void reg(HttpServletRequest request, HttpServletResponse response) {
    132. logger.info("开始处理注册");
    133. /*
    134. 1:获取用户在注册页面上输入的注册信息
    135. 2:将注册信息保存起来
    136. 3:给用户回馈一个注册结果页面
    137. */
    138. //1
    139. //调用getParameter时传入的参数应当与页面上表单中输入框的名字一致(输入框name属性的值)
    140. String username = request.getParameter("username");
    141. String password = request.getParameter("password");
    142. String nickname = request.getParameter("nickname");
    143. String ageStr = request.getParameter("age");
    144. logger.info(username + "," + password + "," + nickname + "," + ageStr);
    145. /*
    146. 添加一个判断,要求:如果上述四个信息有null值或者年龄不是数字
    147. 立刻给用户响应一个错误提示页面:reg_info_error.html
    148. 该页面居中显示一行字:注册信息输入有误,请重新注册。
    149. 正则表达式:[0-9]+
    150. */
    151. if (username == null || password == null || nickname == null || ageStr == null ||
    152. !ageStr.matches("[0-9]+")) {
    153. /*
    154. http://localhost:8088/myweb/reg?username=fancq&password=123456&nickname=fcq&age=22
    155. 给浏览器发送的响应内容:
    156. HTTP/1.1 302 Moved Temporarily(CRLF)
    157. Location: /myweb/reg_info_error.html(CRLF)(CRLF)
    158. 浏览器接收到该响应后,根据状态代码302得知服务器希望他自动再发起一次请求
    159. 来请求指定的位置
    160. 指定的位置是哪里?浏览器根据响应头Location得知位置
    161. 此时得到的Location位置为:/myweb/reg_info_error.html
    162. 由于浏览器之前的请求(发起注册):
    163. http://localhost:8088/myweb/reg?username=fancq&password=123456&nickname=fcq&age=22
    164. 因此浏览器理解本次重新请求的路径就是:
    165. http://localhost:8088/myweb/reg_info_error.html
    166. */
    167. response.sendRedirect("/myweb/reg_info_error.html");
    168. return;
    169. }
    170. int age = Integer.parseInt(ageStr);
    171. System.out.println(username + "," + password + "," + nickname + "," + age);
    172. //2
    173. User user = new User(username, password, nickname, age);
    174. File userFile = new File(userDir, username + ".obj");
    175. /*
    176. 判断重名,如果是已注册用户,则响应页面:have_user.html告知
    177. 该页面居中显示一行字:该用户已存在,请重新注册。
    178. */
    179. if (userFile.exists()) {//该文件存在则说明是重复用户
    180. response.sendRedirect("/myweb/have_user.html");
    181. return;
    182. }
    183. try (
    184. FileOutputStream fos = new FileOutputStream(userFile);
    185. ObjectOutputStream oos = new ObjectOutputStream(fos);
    186. ) {
    187. //这里序列化的是注册用户信息,因此是user对象!!!!!!!!!!!!
    188. oos.writeObject(user);
    189. //3
    190. response.sendRedirect("/myweb/reg_success.html");
    191. } catch (IOException e) {
    192. e.printStackTrace();
    193. logger.error(e.getMessage(), e);
    194. }
    195. }
    196. }

    1.2.6ClientHandler(和浏览器建立响应的类)

    1. package com.webserver.core;
    2. import com.webserver.http.EmptyRequestException;
    3. import com.webserver.http.HttpServletRequest;
    4. import com.webserver.http.HttpServletResponse;
    5. import java.io.*;
    6. import java.lang.reflect.InvocationTargetException;
    7. import java.net.Socket;
    8. import java.net.URISyntaxException;
    9. import java.nio.charset.StandardCharsets;
    10. import java.util.HashMap;
    11. import java.util.Locale;
    12. import java.util.Map;
    13. /**
    14. * 该线程任务是负责与一个客户端进行一次HTTP交互
    15. * 浏览器与服务端交互组从一问一答的原则。因此服务端处理一次HTTP交互,步骤如下:
    16. * 1:解析请求(接受浏览器发送过来的请求内容)
    17. * 2:处理请求(根据浏览器发送的请求理解其意图并进行对应的处理)
    18. * 3:发送响应(将处理结果发送给浏览器)
    19. *
    20. *
    21. */
    22. public class ClientHandler implements Runnable{
    23. private Socket socket;
    24. public ClientHandler(Socket socket){
    25. this.socket = socket;
    26. }
    27. public void run() {
    28. try {
    29. //1解析请求
    30. HttpServletRequest request = new HttpServletRequest(socket);
    31. HttpServletResponse response = new HttpServletResponse(socket);
    32. //2处理请求
    33. DispatcherServlet servlet = new DispatcherServlet();
    34. servlet.service(request,response);
    35. //3发送响应
    36. response.response();
    37. } catch (IOException e) {
    38. e.printStackTrace();
    39. } catch (EmptyRequestException e) {
    40. } catch (ClassNotFoundException e) {
    41. e.printStackTrace();
    42. } catch (InvocationTargetException e) {
    43. e.printStackTrace();
    44. } catch (InstantiationException e) {
    45. e.printStackTrace();
    46. } catch (IllegalAccessException e) {
    47. e.printStackTrace();
    48. } finally {
    49. try {
    50. //按照HTTP协议要求,一次交互后要断开连接
    51. socket.close();
    52. } catch (IOException e) {
    53. e.printStackTrace();
    54. }
    55. }
    56. }
    57. }

    1.2.7DispatcherServlet(处理业务的中转)

    1. package com.webserver.core;
    2. import com.webserver.annotations.Controller;
    3. import com.webserver.annotations.RequestMapping;
    4. import com.webserver.controller.ArticleController;
    5. import com.webserver.controller.ToolsController;
    6. import com.webserver.controller.UserController;
    7. import com.webserver.http.HttpServletRequest;
    8. import com.webserver.http.HttpServletResponse;
    9. import java.io.File;
    10. import java.io.IOException;
    11. import java.lang.reflect.InvocationTargetException;
    12. import java.lang.reflect.Method;
    13. import java.net.URISyntaxException;
    14. import java.nio.file.Files;
    15. /**
    16. * 负责处理请求环节
    17. */
    18. public class DispatcherServlet {
    19. private static File rootDir;
    20. private static File staticDir;
    21. static {
    22. try {
    23. rootDir = new File(
    24. ClientHandler.class.getClassLoader()
    25. .getResource(".").toURI()
    26. );
    27. staticDir = new File(rootDir, "static");
    28. } catch (URISyntaxException e) {
    29. e.printStackTrace();
    30. }
    31. }
    32. public void service(HttpServletRequest request, HttpServletResponse response) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
    33. String path = request.getRequestURI();
    34. //path:/myweb/index.html
    35. //判断当前请求是否为请求业务
    36. HandlerMapping.MethodMapping mm = HandlerMapping.getMethod(path);
    37. if(mm!=null){
    38. Method method = mm.getMethod();
    39. Object controller = mm.getController();
    40. method.invoke(controller,request,response);
    41. return;
    42. }
    43. File file = new File(staticDir, path);
    44. System.out.println("资源是否存在:" + file.exists());
    45. if (file.isFile()) {
    46. //将请求的实际文件设置到response的正文上等待响应
    47. response.setContentFile(file);
    48. } else {
    49. file = new File(staticDir, "/root/404.html");
    50. //将404内容设置到response上等待响应
    51. response.setStatusCode(404);
    52. response.setStatusReason("NotFound");
    53. response.setContentFile(file);
    54. }
    55. }
    56. }

    1.2.8HandlerMapping(通过反射机制读取注解中的value)

    1. package com.webserver.core;
    2. import com.webserver.annotations.Controller;
    3. import com.webserver.annotations.RequestMapping;
    4. import java.io.File;
    5. import java.lang.reflect.Method;
    6. import java.net.URISyntaxException;
    7. import java.util.HashMap;
    8. import java.util.Map;
    9. /**
    10. * 使用当前类维护请求路径与对应的Controller中处理该请求的方法
    11. * 使得DispatcherServlet在处理请求时判断是否为业务可以使用当前类完成。
    12. */
    13. public class HandlerMapping {
    14. public static Map<String,MethodMapping> mapping = new HashMap();
    15. static{
    16. initMapping();
    17. }
    18. private static void initMapping(){
    19. try {
    20. File rootDir = new File(HandlerMapping.class.getClassLoader().
    21. getResource(".").toURI());
    22. File dir = new File(rootDir,"/com/webserver/controller");
    23. File[] files = dir.listFiles(f->f.getName().endsWith(".class"));
    24. for(File file : files){
    25. String fileName = file.getName();
    26. String className = fileName.substring(0,fileName.indexOf("."));
    27. Class cls = Class.forName("com.webserver.controller."+className);
    28. //判断当前类是否为Controller(是否被@Controller标注)
    29. if(cls.isAnnotationPresent(Controller.class)){
    30. //将该Controller实例化
    31. Object controller = cls.newInstance();
    32. //获取该类中所有方法
    33. Method[] methods = cls.getDeclaredMethods();
    34. for(Method method : methods){
    35. //判断当前方法是否为处理业务的方法(是否被@RequestMapping标注)
    36. if(method.isAnnotationPresent(RequestMapping.class)){
    37. //获取注解@RequestMapping
    38. RequestMapping rm = method.getAnnotation(RequestMapping.class);
    39. /*
    40. 比如:
    41. @Controller
    42. public class UserController{
    43. @RequestMapping("/myweb/reg")
    44. public void reg(HttpServletRequest request,HttpServletResponse response){...}
    45. ...
    46. }
    47. Object controller : UserController对象
    48. Method method : reg()方法
    49. String value : "/myweb/reg"
    50. */
    51. String value = rm.value();//该方法处理的请求路径
    52. MethodMapping mm = new MethodMapping(method,controller);
    53. mapping.put(value,mm);
    54. }
    55. }
    56. }
    57. }
    58. } catch (URISyntaxException e) {
    59. e.printStackTrace();
    60. } catch (ClassNotFoundException e) {
    61. e.printStackTrace();
    62. } catch (InstantiationException e) {
    63. e.printStackTrace();
    64. } catch (IllegalAccessException e) {
    65. e.printStackTrace();
    66. }
    67. }
    68. /**
    69. * 根据请求路径获取处理该请求的Controller以及对应的方法
    70. * @param uri
    71. * @return
    72. */
    73. public static MethodMapping getMethod(String uri){
    74. return mapping.get(uri);
    75. }
    76. public static void main(String[] args) {
    77. mapping.forEach(
    78. (k,v)->{
    79. System.out.println("请求路径"+k);
    80. System.out.println("对应的处理方法:"+v.getMethod().getName());
    81. }
    82. );
    83. }
    84. public static class MethodMapping{
    85. //method.invoke(controller,...);
    86. private Method method; //方法对象
    87. private Object controller; //方法所属的Controller对象
    88. public MethodMapping(Method method, Object controller) {
    89. this.method = method;
    90. this.controller = controller;
    91. }
    92. public Method getMethod() {
    93. return method;
    94. }
    95. public Object getController() {
    96. return controller;
    97. }
    98. }
    99. }

    1.2.9WebServerApplication(项目的启动类)

    1. package com.webserver.core;
    2. import java.io.IOException;
    3. import java.net.ServerSocket;
    4. import java.net.Socket;
    5. import java.util.concurrent.Executor;
    6. import java.util.concurrent.ExecutorService;
    7. import java.util.concurrent.Executors;
    8. /**
    9. * WebServer主类
    10. * WebServer是一个Web容器,实现了Tomcat的基础功能。
    11. * 通过本次项目我们的目标是:
    12. * 1:理解Tomcat底层工作原理
    13. * 2:理解HTTP协议的规定
    14. * 3:理解SpringBoot
    15. */
    16. public class WebServerApplication {
    17. private ServerSocket serverSocket;
    18. private ExecutorService threadPool;
    19. /**
    20. * 初始化WebServer
    21. */
    22. public WebServerApplication(){
    23. try {
    24. System.out.println("正在启动服务端...");
    25. /*
    26. 当端口被其他程序占用时,这里会抛出异常:
    27. java.net.BindException:address already in use:JVM
    28. 解决:
    29. 1:杀死该java进程(推荐)
    30. 2:重启电脑(不推荐)
    31. 3:更换端口(通常重启后还不行,说明被其他程序占用该端口了)
    32. */
    33. serverSocket = new ServerSocket(8088);
    34. threadPool = Executors.newFixedThreadPool(50);
    35. System.out.println("服务端启动完毕!");
    36. } catch (IOException e) {
    37. e.printStackTrace();
    38. }
    39. }
    40. /**
    41. * 服务端开始工作的方法
    42. */
    43. public void start(){
    44. try {
    45. while(true) {
    46. System.out.println("等待客户端连接...");
    47. Socket socket = serverSocket.accept();
    48. System.out.println("一个客户端连接了!");
    49. //启动一个线程处理该客户端交互
    50. ClientHandler clientHandler = new ClientHandler(socket);
    51. threadPool.execute(clientHandler);
    52. }
    53. } catch (IOException e) {
    54. e.printStackTrace();
    55. }
    56. }
    57. public static void main(String[] args) {
    58. WebServerApplication webServerApplication = new WebServerApplication();
    59. webServerApplication.start();
    60. }
    61. }

    1.2.10Article(文章实体类)

    1. package com.webserver.entity;
    2. import java.io.Serializable;
    3. public class Article implements Serializable {
    4. public static final long serialVersionUID = 1L;
    5. private String title;
    6. private String author;
    7. private String content;
    8. public Article(String title, String author, String content) {
    9. this.title = title;
    10. this.author = author;
    11. this.content = content;
    12. }
    13. public String getTitle() {
    14. return title;
    15. }
    16. public void setTitle(String title) {
    17. this.title = title;
    18. }
    19. public String getAuthor() {
    20. return author;
    21. }
    22. public void setAuthor(String author) {
    23. this.author = author;
    24. }
    25. public String getContent() {
    26. return content;
    27. }
    28. public void setContent(String content) {
    29. this.content = content;
    30. }
    31. }

    1.2.11User(用户实体类)

    1. package com.webserver.entity;//实体
    2. import java.io.Serializable;
    3. /**
    4. * User的每一个实例用于表示一个注册用户信息
    5. */
    6. public class User implements Serializable {
    7. public static final long serialVersionUID = 1L;
    8. private String username;
    9. private String password;
    10. private String nickname;
    11. private int age;
    12. public User(){}
    13. public User(String username, String password, String nickname, int age) {
    14. this.username = username;
    15. this.password = password;
    16. this.nickname = nickname;
    17. this.age = age;
    18. }
    19. public String getUsername() {
    20. return username;
    21. }
    22. public void setUsername(String username) {
    23. this.username = username;
    24. }
    25. public String getPassword() {
    26. return password;
    27. }
    28. public void setPassword(String password) {
    29. this.password = password;
    30. }
    31. public String getNickname() {
    32. return nickname;
    33. }
    34. public void setNickname(String nickname) {
    35. this.nickname = nickname;
    36. }
    37. public int getAge() {
    38. return age;
    39. }
    40. public void setAge(int age) {
    41. this.age = age;
    42. }
    43. @Override
    44. public String toString() {
    45. return "User{" +
    46. "username='" + username + '\'' +
    47. ", password='" + password + '\'' +
    48. ", nickname='" + nickname + '\'' +
    49. ", age=" + age +
    50. '}';
    51. }
    52. }

    1.2.12EmptyRequestException(处理数组下标越界那个异常)

    1. package com.webserver.http;
    2. /**
    3. * 空请求异常
    4. * 当HttpServletRequest解析请求过程中发现本次是空请求则会抛出该异常
    5. */
    6. public class EmptyRequestException extends Exception{
    7. public EmptyRequestException() {
    8. }
    9. public EmptyRequestException(String message) {
    10. super(message);
    11. }
    12. public EmptyRequestException(String message, Throwable cause) {
    13. super(message, cause);
    14. }
    15. public EmptyRequestException(Throwable cause) {
    16. super(cause);
    17. }
    18. public EmptyRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    19. super(message, cause, enableSuppression, writableStackTrace);
    20. }
    21. }

    1.2.13HttpServletRequest(解析浏览器发送过来的请求)

    1. package com.webserver.http;
    2. import java.io.IOException;
    3. import java.io.InputStream;
    4. import java.io.UnsupportedEncodingException;
    5. import java.net.Socket;
    6. import java.net.URLDecoder;
    7. import java.nio.charset.StandardCharsets;
    8. import java.util.HashMap;
    9. import java.util.Map;
    10. /**
    11. * 请求对象
    12. * 该类的每一个实例用于表示客户端发送过来的一个HTTP请求
    13. * 每个请求由三部分构成:
    14. * 请求行,消息头,消息正文
    15. */
    16. public class HttpServletRequest {
    17. private Socket socket;
    18. //请求行的相关信息
    19. private String method;//请求方式
    20. private String uri;//抽象路径
    21. private String protocol;//协议版本
    22. private String requestURI;//uri中的请求部分,"?"左侧的内容
    23. private String queryString;//uri中的参数部分,"?"右侧的内容
    24. private Map<String,String> parameters = new HashMap<>();//每一组参数 key:参数名 value:参数值
    25. //用一个Map保存所有的消息头,其中key:消息头名字,value:消息头的值
    26. private Map<String,String> headers = new HashMap<>();
    27. public HttpServletRequest(Socket socket) throws IOException, EmptyRequestException {
    28. this.socket = socket;
    29. //1.1解析请求行
    30. parseRequestLine();
    31. //1.2解析消息头
    32. parseHeaders();
    33. //1.3解析消息正文
    34. parseContent();
    35. }
    36. //解析请求行
    37. private void parseRequestLine() throws IOException, EmptyRequestException {
    38. String line = readLine();
    39. if(line.isEmpty()){//读取请求行是空串,则说明本次为空请求
    40. throw new EmptyRequestException();
    41. }
    42. System.out.println(line);
    43. //将请求行按照空格拆分为三部分,并赋值给三个变量
    44. String[] data = line.split("\\s");
    45. method = data[0];
    46. uri = data[1];
    47. protocol = data[2];
    48. parseURI();//进一步解析uri
    49. //测试路径:http://localhost:8088/myweb/index.html
    50. System.out.println("method:"+method);//method:GET
    51. System.out.println("uri:"+uri);//uri:/myweb/index.html
    52. System.out.println("protocol:"+protocol);//protocol:HTTP/1.1
    53. }
    54. //进一步解析URI
    55. private void parseURI(){
    56. /*
    57. uri有两种情况:
    58. 1:不含有参数的
    59. 例如: /myweb/index.html
    60. 直接将uri的值赋值给requestURI即可.
    61. 2:含有参数的
    62. 例如:/myweb/reg?username=fancq&password=123456&nickname=chuanqi&age=22
    63. 将uri中"?"左侧的请求部分赋值给requestURI
    64. 将uri中"?"右侧的参数部分赋值给queryString
    65. 将参数部分首先按照"&"拆分出每一组参数,再将每一组参数按照"="拆分为参数名与参数值
    66. 并将参数名作为key,参数值作为value存入到parameters中。
    67. */
    68. String[] data = uri.split("\\?");
    69. requestURI = data[0];
    70. if(data.length>1){
    71. queryString = data[1];
    72. parseParameters(queryString);
    73. }
    74. System.out.println("requestURI:"+requestURI);
    75. System.out.println("queryString:"+queryString);
    76. System.out.println("parameters:"+parameters);
    77. }
    78. //解析参数
    79. private void parseParameters(String line){
    80. //将参数部分中的%XX内容转换为对应字符
    81. try {
    82. line = URLDecoder.decode(line,"UTF-8");
    83. } catch (UnsupportedEncodingException e) {
    84. e.printStackTrace();
    85. }
    86. //首先按照"&"拆分出每一组参数
    87. String[] data = line.split("&");
    88. //遍历拆分后的数组取出每一组参数
    89. for(String para : data){//username=fancq
    90. //将每一组参数按照"="拆分为参数名和参数值
    91. String[] paraArray = para.split("=");
    92. //将参数名和参数值分别以key,value形式存入parameters(Map中)
    93. parameters.put(paraArray[0], paraArray.length>1?paraArray[1]:null);
    94. }
    95. }
    96. //解析消息头
    97. private void parseHeaders() throws IOException {
    98. while(true) {
    99. String line = readLine();
    100. if(line.isEmpty()){//读取单独读取了CRLF则停止循环
    101. break;
    102. }
    103. System.out.println(line);
    104. String[] data = line.split(":\\s");
    105. headers.put(data[0].toLowerCase(),data[1]);
    106. }
    107. System.out.println(headers);
    108. }
    109. //解析消息正文
    110. private void parseContent() throws IOException {
    111. /*
    112. 1:如何确定含有正文?
    113. post请求才含有正文
    114. 判断请求方式是否为post请求
    115. 2:如果存在正文,则要先将正文读取回来。读取多少个字节呢?
    116. 通过消息头:Content-Length得知正文长度以便读取
    117. 3:如何判定该正文的所有字节表示什么内容呢?
    118. 通过消息头:Content-Type得知
    119. 这里我们仅处理一种:form表单提交的用户输入信息
    120. Content-Type: application/x-www-form-urlencoded
    121. 如果是上面的类型,则正文是一个字符串,内容就是原GET请求提交时
    122. 在抽象路径中"?"右侧的内容。
    123. 格式如:
    124. username=xxxxx&password=xxxx&....
    125. 因此当我们将正文转换为字符串后,要拆分参数来初始化:parameters
    126. 实际这里将来要写分支判断不同的类型,来进行不同的处理。
    127. */
    128. if("post".equalsIgnoreCase(method)){
    129. //获取Content-Length
    130. String contentLength = getHeader("content-length");
    131. if(contentLength!=null){//确保存在正文长度
    132. //将长度转换为int值
    133. int length = Integer.parseInt(contentLength);
    134. System.out.println("正文长度:"+length);
    135. //根据正文长度将正文内容读取回来
    136. InputStream in = socket.getInputStream();
    137. byte[] data = new byte[length];
    138. in.read(data);
    139. //根据Content-Type的类型来对正文数据进行解析
    140. String contentType = getHeader("content-type");
    141. //将来根据不同的值对正文进行不同的解析操作,这里仅处理form提交的数据
    142. if("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)){
    143. String line = new String(data, StandardCharsets.ISO_8859_1);
    144. System.out.println("正文内容:"+line);
    145. parseParameters(line);
    146. }
    147. //这里后期继续else if分支判断其他类型...
    148. }
    149. }
    150. }
    151. /**
    152. * 将读取一行字符串的操作定义成方法进行重用
    153. * 通常复用代码的方法中如果需要处理异常时,通常都是直接抛出给调用者处理
    154. * @return
    155. */
    156. private String readLine() throws IOException {
    157. /*
    158. socket对象无论调用多少次getInputStream和getOutputStream
    159. 获取回来的始终是同一条输入或输出流
    160. */
    161. InputStream in = socket.getInputStream();
    162. int d ;
    163. char cur='a',pre='a';//cur表示本次读取的字符,pre表示上次读取的字符
    164. StringBuilder builder = new StringBuilder();
    165. while((d = in.read()) != -1){
    166. cur = (char)d;//本次读取到的字符
    167. if(pre==13&&cur==10){//是否连续读取了回车+换行
    168. break;
    169. }
    170. builder.append(cur);
    171. pre = cur;//在进行下一轮读取前,要将本次读取的字符记作"上次读取的字符"
    172. }
    173. return builder.toString().trim();//获取拼接好的一行字符串内容
    174. }
    175. public String getMethod() {
    176. return method;
    177. }
    178. public String getUri() {
    179. return uri;
    180. }
    181. public String getProtocol() {
    182. return protocol;
    183. }
    184. public String getHeader(String name) {
    185. return headers.get(name);
    186. }
    187. public String getRequestURI() {
    188. return requestURI;
    189. }
    190. public String getQueryString() {
    191. return queryString;
    192. }
    193. /**
    194. * 根据参数名获取对应的参数值
    195. * @param name
    196. * @return
    197. */
    198. public String getParameter(String name) {
    199. //paratemers key:参数名(页面表单中输入框的名字) value:参数值(输入框上用户输入的信息)
    200. return parameters.get(name);
    201. }
    202. }

    1.2.14HttpServletResponse(处理用户发送给浏览器的请求)

    1. package com.webserver.http;
    2. import java.io.*;
    3. import java.net.Socket;
    4. import java.nio.charset.StandardCharsets;
    5. import java.nio.file.Files;
    6. import java.util.HashMap;
    7. import java.util.Map;
    8. import java.util.Map.Entry;
    9. import java.util.Set;
    10. /**
    11. * 响应对象
    12. * 该类的每一个实例用于表示发送给客户端(浏览器)的一个HTTP响应内容
    13. * 每个响应由三部分构成:
    14. * 状态行,响应头,响应正文
    15. */
    16. public class HttpServletResponse {
    17. private Socket socket;
    18. //状态行相关信息
    19. private int statusCode = 200;//状态代码,默认为200
    20. private String statusReason = "OK";//状态描述,默认为OK
    21. //响应头相关信息
    22. //key:响应头名字 value:响应头的值
    23. private Map<String,String> headers = new HashMap<>();
    24. //响应正文相关信息
    25. private File contentFile;//响应正文对应的实体文件
    26. private byte[] contentData;//以一组字节作为正文内容(通常动态数据使用)
    27. private ByteArrayOutputStream baos;
    28. public HttpServletResponse(Socket socket){
    29. this.socket = socket;
    30. }
    31. /**
    32. * 将当前响应对象内容按照标准的HTTP响应格式发送给浏览器
    33. */
    34. public void response() throws IOException {
    35. //发送前的准备工作
    36. sendBefore();
    37. //3.1发送状态行
    38. sendStatusLine();
    39. //3.2发送响应头
    40. sendHeaders();
    41. //3.3将文件的所有字节作为正文内容发送给浏览器
    42. sendContent();
    43. }
    44. //响应发送前的准备工作
    45. private void sendBefore(){
    46. if(baos!=null){//如果baos不为null,则说明用过这个流写出过动态数据
    47. contentData = baos.toByteArray();//将内部的字节数组获取(动态数据)
    48. addHeader("Content-Length",contentData.length+"");
    49. }
    50. }
    51. //发送状态行
    52. private void sendStatusLine() throws IOException {
    53. println("HTTP/1.1" + " " + statusCode + " " + statusReason);
    54. }
    55. //发送响应头
    56. private void sendHeaders() throws IOException {
    57. /*
    58. headers
    59. KEY VALUE
    60. Content-Type text/html
    61. Content-Length 2323
    62. ... ...
    63. */
    64. //遍历headers将所有的响应头发送给浏览器
    65. Set<Entry<String,String>> entrySet = headers.entrySet();
    66. for(Entry<String,String> e : entrySet){
    67. String key = e.getKey();
    68. String value = e.getValue();
    69. //println("Content-Type: text/html");
    70. println(key + ": " + value);
    71. }
    72. //单独发送回车+换行表示响应头部分发送完毕了
    73. println("");
    74. }
    75. //发送响应正文
    76. private void sendContent() throws IOException {
    77. if(contentData!=null){//是否有动态数据作为正文
    78. OutputStream out = socket.getOutputStream();
    79. out.write(contentData);
    80. }else if(contentFile!=null) {
    81. //try()意味着编译后有finally用于遍历流。所以这不违背try不能单独定义的原则
    82. try (
    83. FileInputStream fis = new FileInputStream(contentFile);
    84. ) {
    85. OutputStream out = socket.getOutputStream();
    86. byte[] buf = new byte[1024 * 10];//10kb
    87. int len;//记录每次实际读取到的字节数
    88. while ((len = fis.read(buf)) != -1) {
    89. out.write(buf, 0, len);
    90. }
    91. }
    92. }
    93. }
    94. private void println(String line) throws IOException {
    95. OutputStream out = socket.getOutputStream();
    96. byte[] data = line.getBytes(StandardCharsets.ISO_8859_1);
    97. out.write(data);//发送状态行内容
    98. out.write(13);//发送回车符
    99. out.write(10);//发送换行符
    100. }
    101. /**
    102. * 重定向到uri指定的路径
    103. * @param uri
    104. */
    105. public void sendRedirect(String uri){
    106. //1设置状态代码位302
    107. statusCode = 302;
    108. statusReason = "Moved Temporarily";
    109. //2添加必要的响应头Location指定浏览器再次请求的位置
    110. addHeader("Location",uri);
    111. }
    112. public int getStatusCode() {
    113. return statusCode;
    114. }
    115. public void setStatusCode(int statusCode) {
    116. this.statusCode = statusCode;
    117. }
    118. public String getStatusReason() {
    119. return statusReason;
    120. }
    121. public void setStatusReason(String statusReason) {
    122. this.statusReason = statusReason;
    123. }
    124. public File getContentFile() {
    125. return contentFile;
    126. }
    127. public void setContentFile(File contentFile) {
    128. this.contentFile = contentFile;
    129. /*
    130. 根据正文文件类型来设置Content-Type用于告知浏览器该正文类型以便其理解.
    131. 但是在HTTP协议中规定:如果发送响应时,不包含Content-Type响应头时,则是
    132. 让浏览器自行理解正文类型.
    133. */
    134. try {
    135. //该方法会自动分析文件对应的Content-Type值,若无法识别会返回null
    136. //http://localhost:8088/TeduStore/index.html
    137. /*
    138. 比如:file表示的是 index.html 文件
    139. 该方法返回值为:"text/html"
    140. file表示的是 jquery.js 文件
    141. 该方法那会的值为:"application/javascript"
    142. */
    143. String type = Files.probeContentType(contentFile.toPath());
    144. if(type!=null) {
    145. addHeader("Content-Type", type);
    146. }
    147. } catch (IOException e) {
    148. }
    149. addHeader("Content-Length",contentFile.length()+"");
    150. }
    151. /**
    152. * 添加一个响应头
    153. * @param name
    154. * @param value
    155. */
    156. public void addHeader(String name,String value){
    157. headers.put(name,value);
    158. }
    159. public OutputStream getOutputStream(){
    160. if(baos==null){
    161. baos = new ByteArrayOutputStream();
    162. }
    163. return baos;
    164. }
    165. public PrintWriter getWriter(){
    166. /*
    167. 进行流连接,创建一个PrintWrtier并最终连接到属性baos上
    168. 将该PrintWriter返回给外界使用。
    169. */
    170. OutputStream out = getOutputStream();//baos
    171. OutputStreamWriter osw = new OutputStreamWriter(out,StandardCharsets.UTF_8);
    172. BufferedWriter bw = new BufferedWriter(osw);
    173. return new PrintWriter(bw,true);
    174. // return new PrintWriter(
    175. // new BufferedWriter(
    176. // new OutputStreamWriter(
    177. // getOutputStream(),StandardCharsets.UTF_8
    178. // )
    179. // ),true
    180. // );
    181. }
    182. /**
    183. * 添加响应头Content-Type以及对应的值
    184. * @param mime
    185. */
    186. public void setContentType(String mime){
    187. addHeader("Content-Type",mime);
    188. }
    189. }

    1.3HTML代码

    1.3.1article_info_error.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>失败</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>文章输入信息有误,请<a href="/myweb/writeArticle.html">重新发表</a></h1>
    10. </center>
    11. </body>
    12. </html>

    1.3.2article_success.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>成功</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>发表成功!</h1>
    10. </center>
    11. </body>
    12. </html>

    1.3.3classTable.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>课程表</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>课程表</h1>
    10. <table border="1">
    11. <tr>
    12. <td>&nbsp;</td>
    13. <td>星期一</td>
    14. <td>星期二</td>
    15. <td>星期三</td>
    16. <td>星期四</td>
    17. <td>星期五</td>
    18. </tr>
    19. <tr>
    20. <td rowspan="4">上午</td>
    21. <td>语文</td>
    22. <td>数学</td>
    23. <td>英语</td>
    24. <td>体育</td>
    25. <td>生物</td>
    26. </tr>
    27. <tr>
    28. <td>生物</td>
    29. <td>数学</td>
    30. <td>英语</td>
    31. <td>语文</td>
    32. <td>体育</td>
    33. </tr>
    34. <tr>
    35. <td>生物</td>
    36. <td>数学</td>
    37. <td>英语</td>
    38. <td>语文</td>
    39. <td>体育</td>
    40. </tr>
    41. <tr>
    42. <td>生物</td>
    43. <td>数学</td>
    44. <td>英语</td>
    45. <td>语文</td>
    46. <td>体育</td>
    47. </tr>
    48. <tr>
    49. <td colspan="6" align="center"><a href="http://www.bilibili.com">午休,bilibili一下</a></td>
    50. </tr>
    51. <tr>
    52. <td rowspan="4">下午</td>
    53. <td>语文</td>
    54. <td>数学</td>
    55. <td>英语</td>
    56. <td>体育</td>
    57. <td>生物</td>
    58. </tr>
    59. <tr>
    60. <td>生物</td>
    61. <td>数学</td>
    62. <td>英语</td>
    63. <td>语文</td>
    64. <td>体育</td>
    65. </tr>
    66. <tr>
    67. <td>生物</td>
    68. <td>数学</td>
    69. <td>英语</td>
    70. <td>语文</td>
    71. <td>体育</td>
    72. </tr>
    73. <tr>
    74. <td>生物</td>
    75. <td>数学</td>
    76. <td>英语</td>
    77. <td>语文</td>
    78. <td>体育</td>
    79. </tr>
    80. </table>
    81. </center>
    82. </body>
    83. </html>

    1.3.4createQR.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>二维码生成器</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>二维码生成器</h1>
    10. <form action="/myweb/createQR" method="post">
    11. <table border="1">
    12. <tr>
    13. <td>内容</td>
    14. <td><input type="text" name="content"></td>
    15. </tr>
    16. <tr>
    17. <td align="center" colspan="2"><input type="submit" value="生成"></td>
    18. </tr>
    19. </table>
    20. </form>
    21. </center>
    22. </body>
    23. </html>

    1.3.5have_article.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>失败</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>该文章已经存在了,请<a href="/myweb/writeArticle.html">重新发表</a></h1>
    10. </center>
    11. </body>
    12. </html>

    1.3.6have_user.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>失败</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>该用户已存在,请<a href="/myweb/reg.html">重新注册</a></h1>
    10. </center>
    11. </body>
    12. </html>

    1.3.7index.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>我的首页</title>
    6. </head>
    7. <body>
    8. <!--
    9. <h1>-<h6>:标题,分为1-6级标题。标题独占一行
    10. <center>:居中,该标签在HTML5中已经不再建议使用了。没学习样式前临时使用
    11. <input>:输入组件。它是一套组件,用来在页面上获取用户输入的。
    12. 属性type决定着该组件的样式。
    13. 常见的值有:
    14. text:文本框
    15. button:按钮
    16. checkbox:多选框
    17. radio:单选框
    18. submit:提交按钮
    19. password:密码框
    20. <a>:超链接,标签中间的文本是超链接对应的文字,属性href用于指定跳转的路径
    21. <br>:换行
    22. <table>标签:表格。属性border用于指定边框。
    23. <table>标签中包含<tr>标签用于表示行
    24. <tr>标签中包含<td>标签用于表示列
    25. <td>标签中常见属性:
    26. align:对其方式。left左对齐,right右对齐,center剧中对其
    27. colspan:跨列合并列,合并是从左向右合并列
    28. rowspan:跨行合并列,合并是从上向下合并列
    29. <img>:图片标签,src属性用于指定图片的路径
    30. 在页面上我们经常会使用路径去定位页面上要使用的额外资源,比如CSS文件,JS文件
    31. 图片文件等。
    32. 例如图片:
    33. <img src=""> 这里的src就是用于指定图片的路径的
    34. 路径分两种:相对路径和绝对路径
    35. 相对路径常用的:"./",即:当前目录
    36. 在浏览器中和我们在java源代码中"./"位置不是相同的!!!!!!
    37. 原因是页面是被浏览器理解的。
    38. 在页面上"./"的位置浏览器理解的是:URL地址当中抽象路径里的最后一个"/"的位置
    39. 例如:
    40. 请求inde.html时,我们在地址栏上输入的路径为:
    41. http://localhost:8088/myweb/index.html
    42. 在这个页面上我们使用了<img src="logo.png"> 注:不写"./"默认就是从"./"开始
    43. 此时浏览器判定该图片的实际位置是哪里,就是根据请求当前页面的URL地址决定:
    44. 当前URL地址中抽象路径部分为:/myweb/index.html
    45. 因此"./"理解的就是这里最后一个"/"的位置为:/myweb/
    46. ^
    47. ./的位置
    48. 所以确定了图片的抽象路径部分为:/myweb/logo.png
    49. 因此浏览器实际请求图片的路径为:
    50. http://localhost:8088/myweb/logo.png
    51. 绝对路径:"/",即:根
    52. 在页面上"/"的位置浏览器理解的是:URL地址当中抽象路径里的第一个"/"的位置
    53. 同样,如果在当前页面上<img src="/myweb/logo.png">
    54. 此时浏览器理解的"/myweb/logo.png"发先路径从"/"开始,即:从根开始。
    55. 请求当前页面路径:http://localhost:8088/myweb/index.html
    56. ^
    57. 就是抽象路径的根
    58. 因此该图片实际请求位置:
    59. http://localhost:8088/myweb/logo.png
    60. 相对路径存在定位不准确情况,常见于服务端转发404时
    61. 例如:请求一个不存在的页面
    62. http://localhost:8088/myweb/123.html
    63. 服务端发现该页面不存在后,就响应了root目录下的404.html
    64. 而404页面上我们指定图片如果为:<img src="404.png">
    65. 那么浏览器在得到404页面时,理解的404图片实际位置是哪里?
    66. 由于浏览器是请求 http://localhost:8088/myweb/123.html看到的404页面
    67. 因此浏览器会参考该地址中抽象路径部分:/myweb/123.html来分析404图片位置
    68. 由于"./404.png"中的"./"是当前目录,因此浏览器认为该图片的抽象路径应当为:
    69. "/myweb/404.png"于是请求了:http://localhost:8088/myweb/404.png
    70. 为了定位准确,我们选取绝对路径中的根"/"
    71. <img src="/root/404.png">
    72. -->
    73. <center>
    74. <!--<h1>百度</h1>-->
    75. <img src="/myweb/logo.png"><br>
    76. <input type="text" size="32">
    77. <input type="button" value="百度一下" onclick="alert('点你妹啊!')">
    78. <br>
    79. <a href="/myweb/reg.html">注册</a>
    80. <a href="/myweb/login.html">登录</a>
    81. <a href="/myweb/showAllUser">用户列表</a>
    82. <br>
    83. <a href="/myweb/writeArticle.html">发表文章</a>
    84. <br>
    85. <a href="/myweb/createQR.html">二维码生成器</a>
    86. <br>
    87. <a href="http://www.taobao.com">淘宝</a>
    88. <br>
    89. <table border="1">
    90. <tr>
    91. <td>第一列</td>
    92. <td>第二列</td>
    93. <td colspan="2" align="center">第三列</td>
    94. </tr>
    95. <tr>
    96. <td rowspan="2">第一列</td>
    97. <td>第二列</td>
    98. <td>第三列</td>
    99. <td>第四列</td>
    100. </tr>
    101. <tr>
    102. <td>第二列</td>
    103. <td>第三列</td>
    104. <td>第四列</td>
    105. </tr>
    106. </table>
    107. </center>
    108. </body>
    109. </html>

    1.3.8login.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>用户登录</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>用户登录</h1>
    10. <form action="/myweb/login" method="post">
    11. <table border="1">
    12. <tr>
    13. <td>用户名</td>
    14. <td><input name="username" type="text"></td>
    15. </tr>
    16. <tr>
    17. <td>密码</td>
    18. <td><input name="password" type="password"></td>
    19. </tr>
    20. <tr>
    21. <td colspan="2" align="center"><input type="submit" value="登录"></td>
    22. </tr>
    23. </table>
    24. </form>
    25. </center>
    26. </body>
    27. </html>

    1.3.9login_fail.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>失败</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>登录失败,用户名或密码不正确!请<a href="/myweb/login.html">重新登录</a></h1>
    10. </center>
    11. </body>
    12. </html>

    1.3.10login_info_error.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>错误</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>登录信息输入有误,请<a href="/myweb/login.html">重新登录</a></h1>
    10. </center>
    11. </body>
    12. </html>

    1.3.11login_success.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>成功</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>登录成功,欢迎回来!</h1>
    10. </center>
    11. </body>
    12. </html>

    1.3.12reg.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>用户注册</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>用户注册</h1>
    10. <!--
    11. 表单组件<form>
    12. 表单用于将用于在页面上输入的信息提交给服务端使用的组件。
    13. 表单上有两个重要的属性:
    14. action:用于指定表单提交的路径
    15. method:用于指定表单提交的方式,该方式有两个可选值
    16. GET:地址栏形式提交,表单数据会被拼接到URL的抽象路径中
    17. POST:打包提交,表单数据会被包含在请求的消息正文中。
    18. 当表单数据含有用户隐私信息或附件时则需要使用POST形式提交表单。
    19. method属性不指定时,默认为GET形式提交表单。
    20. 注意:所有输入组件只有被包含在form表单中并且必须使用name属性为该组件取名字才会被提交!!
    21. 输入组件取的名字可以任意定义,但是不能有中文(英文数字)。
    22. http://localhost:8088/myweb/reg?username=fancq&password=123456&nickname=chuanqi&age=22
    23. 表单提交后的URL中抽象路径:
    24. /myweb/reg?username=fancq&password=123456&nickname=chuanqi&age=22
    25. URL地址中抽象路径部分可以使用"?"分隔为两块,分别是请求部分和参数部分
    26. 请求部分由表单action决定.参数部分由表单中输入框决定
    27. 格式:
    28. 请求部分?输入框1名字=输入框1信息&输入框2名字=输入框2信息&.....
    29. -->
    30. <form action="/myweb/reg" method="get">
    31. <table border="1">
    32. <tr>
    33. <td>用户名</td>
    34. <td><input name="username" type="text"></td>
    35. </tr>
    36. <tr>
    37. <td>密码</td>
    38. <td><input name="password" type="password"></td>
    39. </tr>
    40. <tr>
    41. <td>昵称</td>
    42. <td><input name="nickname" type="text"></td>
    43. </tr>
    44. <tr>
    45. <td>年龄</td>
    46. <td><input name="age" type="text"></td>
    47. </tr>
    48. <tr>
    49. <td><img src="/myweb/random.jpg"></td>
    50. <td><input name="code" type="text"></td>
    51. </tr>
    52. <tr>
    53. <td colspan="2" align="center">
    54. <input type="submit" value="注册">
    55. </td>
    56. </tr>
    57. </table>
    58. </form>
    59. </center>
    60. </body>
    61. </html>

    1.3.13reg_info_error.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>失败</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>注册信息输入有误,请<a href="/myweb/reg.html">重新注册</a></h1>
    10. </center>
    11. </body>
    12. </html>

    1.3.14reg_success.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>成功</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>恭喜您,注册成功了!</h1>
    10. </center>
    11. </body>
    12. </html>

    1.3.15writeArticle.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>发表文章</title>
    6. </head>
    7. <body>
    8. <center>
    9. <h1>发表文章</h1>
    10. <form action="/myweb/writeArticle" method="get">
    11. <table border="1">
    12. <tr>
    13. <td>标题</td>
    14. <td><input name="title" type="text"></td>
    15. </tr>
    16. <tr>
    17. <td>作者</td>
    18. <td><input name="author" type="text"></td>
    19. </tr>
    20. <tr>
    21. <td>内容</td>
    22. <td><textarea cols="50" rows="10" name="content"></textarea></td>
    23. </tr>
    24. <tr>
    25. <td colspan="2" align="center"><input type="submit" value="发表"></td>
    26. </tr>
    27. </table>
    28. </form>
    29. </center>
    30. </body>
    31. </html>

    1.3.16 404.html

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>404</title>
    6. </head>
    7. <body>
    8. <center>
    9. <img src="/root/404.png">
    10. <h1>404,资源不存在!</h1>
    11. </center>
    12. </body>
    13. </html>

    1.4(配置文件)

    1.4.1pom.xml

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <modelVersion>4.0.0</modelVersion>
    6. <groupId>org.example</groupId>
    7. <artifactId>WebServer</artifactId>
    8. <packaging>pom</packaging>
    9. <version>1.0-SNAPSHOT</version>
    10. <modules>
    11. <module>V1</module>
    12. <module>V2</module>
    13. <module>V3</module>
    14. <module>V4</module>
    15. <module>V5</module>
    16. <module>V6</module>
    17. <module>V7</module>
    18. <module>V8</module>
    19. <module>V9</module>
    20. <module>V10</module>
    21. <module>V11</module>
    22. <module>V12</module>
    23. <module>V13</module>
    24. <module>V14</module>
    25. <module>V15</module>
    26. <module>V16</module>
    27. <module>V17</module>
    28. <module>V18</module>
    29. <module>V19</module>
    30. <module>V20</module>
    31. <module>V21</module>
    32. <module>V22</module>
    33. <module>V23</module>
    34. <module>V24</module>
    35. <module>V25</module>
    36. <module>V26</module>
    37. <module>V27</module>
    38. </modules>
    39. <properties>
    40. <!-- 设置 JDK 版本为 1.8 -->
    41. <maven.compiler.target>1.8</maven.compiler.target>
    42. <maven.compiler.source>1.8</maven.compiler.source>
    43. <!-- 设置编码为 UTF-8 -->
    44. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    45. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    46. <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    47. </properties>
    48. <dependencies>
    49. <dependency>
    50. <groupId>org.slf4j</groupId>
    51. <artifactId>slf4j-log4j12</artifactId>
    52. <version>1.7.25</version>
    53. </dependency>
    54. </dependencies>
    55. </project>

    1.4.2log4j.properties

    1. log4j.rootLogger=INFO, console, file
    2. log4j.appender.console=org.apache.log4j.ConsoleAppender
    3. log4j.appender.console.layout=org.apache.log4j.PatternLayout
    4. log4j.appender.console.layout.ConversionPattern=%d %p [%c] - %m%n
    5. log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
    6. log4j.appender.file.File=logs/log.log
    7. log4j.appender.file.layout=org.apache.log4j.PatternLayout
    8. log4j.appender.A3.MaxFileSize=1024KB
    9. log4j.appender.A3.MaxBackupIndex=10
    10. log4j.appender.file.layout.ConversionPattern=%d %p [%c] - %m%n

    1.4.3pom.xml(配置文件中标签的含义)

    1. <project ><!-- 声明规范:pom版本标签(必须要的),表示是一个固定的版本,指定了当前pom的版本 -->
    2. <modelVersion><!--声明项目描述符遵循哪一个POM模型版本。模型本身的版本很少改变,虽然如此,但它仍然是必不可少的,
    3. 这是为了当Maven引入了新的特性或者其他模型变更的时候,确保稳定性。 -->
    4. <groupId><!--项目的组织标识符,对应src/main目录下的Java的目录结构。
    5. 例如,本项目中的com.manage.maven既是项目组织标识符,也是src/main目录下的Java代码包。-->
    6. <artifactId><!--项目的标识符,可用来区分不同的项目。例如,本项目中是hello-maven也可以是test-maven。-->
    7. <packaging><!--项目产生的构件类型,例如jar、war、ear、pom。插件可以创建他们自己的构件类型,所以前面列的不是全部构件类型 -->
    8. <version><!--项目的版本号,项目打包后的JAR文件的版本号跟这里对应。-->
    9. <modules>模块根目录
    10. <module>V1</module>模块
    11. <properties><!--有时候对于同一个技术需要编写很多个依赖引入相关的jar包,每个依赖中都要填写版本号,倘若更换版本号的话,修改的地方比较多,
    12. 所以可以使用properties统一管理版本号,logback-version可以自由命名,通过${}方式进行引用:-->
    13. <!-- 设置 JDK 版本为 1.8 -->
    14. <maven.compiler.target>1.8</maven.compiler.target>
    15. <maven.compiler.source>1.8</maven.compiler.source>
    16. <!-- 设置编码为 UTF-8 -->
    17. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    18. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    19. <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    20. </properties>
    21. <dependencies><!--一个完整的JAR依赖配置。-->
    22. <dependency><!--我们要使用的jar包的gav信息放到这里,这样maven就可以自动下载相关jar包了。-->
    23. <groupId>org.slf4j</groupId>
    24. <artifactId>slf4j-log4j12</artifactId>
    25. <version>1.7.25</version>
    26. </dependency>
    27. </dependencies>
    28. </project>

    1.5代码执行流程

     上诉项目一共写了27个版本想看那个版本做了什么的去看JSD第二阶段

  • 相关阅读:
    Spring Boot 笔记 021 项目部署
    Elk-Metricbeat配置Tomcat的日志分析 (Metricbeat-part3)
    Electron之单例+多窗口
    CenOS7各种自启动配置
    NLP工具——Stanza设置GPU device
    Vue.js 事件监听全解析
    如何利用Api接口获取手机当前的网络位置信息
    Redis启停脚本
    ES6 入门—ES6 迭代器
    【Spring源码】8. 捋下invokeBeanFactoryPostProcessors()主要处理流程
  • 原文地址:https://blog.csdn.net/TheNewSystrm/article/details/125436380