一、实现Response

对回复的抽象处理

实现ConnectorUtils

获取资源主目录对应的路径

  1. package connector;
  2. import java.io.File;
  3. /**
  4. * @ClassName:
  5. * @Description:
  6. * @author: hszjj
  7. * @date: 2019/11/26 12:54
  8. */
  9. public class ConnectorUtils {
  10. public static final String WEB_ROOT =
  11. System.getProperty("user.dir") + File.separator + "webroot";
  12. public static final String PROTOCOL = "HTTP/1.1";
  13. public static final String CARRIAGE = "\r";
  14. public static final String NEWLINE = "\n";
  15. public static final String SPACE = " ";
  16. public static String renderStatus(HttpStatus status){
  17. StringBuilder sb=new StringBuilder(PROTOCOL)
  18. .append(SPACE)
  19. .append(status.getStatusCode())
  20. .append(SPACE)
  21. .append(status.getReason())
  22. .append(CARRIAGE).append(NEWLINE)
  23. .append(CARRIAGE).append(NEWLINE);
  24. return sb.toString();
  25. }
  26. }

实现HttpStatus

获取网络状态

  1. package connector;
  2. /**
  3. * @ClassName:
  4. * @Description:
  5. * @author: hszjj
  6. * @date: 2019/11/26 13:07
  7. */
  8. public enum HttpStatus {
  9. SC_OK(200,"OK"),
  10. SC_NOT_FOUND(404,"File Not Found");
  11. private int statusCode;
  12. private String reason;
  13. HttpStatus(int Code,String reason){
  14. this.statusCode=Code;
  15. this.reason=reason;
  16. }
  17. public int getStatusCode() {
  18. return statusCode;
  19. }
  20. public String getReason() {
  21. return reason;
  22. }
  23. }

实现Response

  1. package connector;
  2. import java.io.*;
  3. /**
  4. * @ClassName:
  5. * @Description:HTTP/1.1 200 OK
  6. * @author: hszjj
  7. * @date: 2019/11/26 12:49
  8. */
  9. public class Response {
  10. private static final int BUFFER_SIZE = 1024;
  11. Request request;
  12. OutputStream output;
  13. public Response(OutputStream output) {
  14. this.output = output;
  15. }
  16. public void setRequest(Request request) {
  17. this.request = request;
  18. }
  19. public void sendStaticResource() throws IOException {
  20. File file = new File(ConnectorUtils.WEB_ROOT, request.getRequestUri());
  21. try {
  22. write(file, HttpStatus.SC_OK);
  23. } catch (IOException e) {
  24. write(new File(ConnectorUtils.WEB_ROOT, "404.html"), HttpStatus.SC_NOT_FOUND);
  25. }
  26. }
  27. private void write(File resource, HttpStatus status) throws IOException {
  28. try (FileInputStream input = new FileInputStream(resource)) {
  29. output.write(ConnectorUtils.renderStatus(status).getBytes());
  30. byte[] buffer = new byte[BUFFER_SIZE];
  31. int lenth = 0;
  32. while ((lenth = input.read(buffer, 0, BUFFER_SIZE)) != -1) {
  33. output.write(buffer, 0, lenth);
  34. }
  35. }
  36. }
  37. }

二、Response测试

TestUtils

工具类

  1. package test.util;
  2. import connector.Request;
  3. import java.io.ByteArrayInputStream;
  4. import java.io.File;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.nio.file.Files;
  8. import java.nio.file.Path;
  9. import java.nio.file.Paths;
  10. /**
  11. * @ClassName:
  12. * @Description:
  13. * @author: hszjj
  14. * @date: 2019/11/26 13:43
  15. */
  16. public class TestUtils {
  17. public static Request createRequest(String requestString){
  18. InputStream input=new ByteArrayInputStream(requestString.getBytes());
  19. Request request=new Request(input);
  20. request.parse();
  21. return request;
  22. }
  23. public static String readFileToString(String filename) throws IOException {
  24. return new String(Files.readAllBytes(Paths.get(filename)));
  25. }
  26. }

ResponseTest

  1. package test.connector;
  2. import connector.ConnectorUtils;
  3. import connector.Request;
  4. import connector.Response;
  5. import org.junit.jupiter.api.Assertions;
  6. import org.junit.jupiter.api.Test;
  7. import test.util.TestUtils;
  8. import java.io.ByteArrayOutputStream;
  9. import java.io.IOException;
  10. /**
  11. * @ClassName:
  12. * @Description:
  13. * @author: hszjj
  14. * @date: 2019/11/26 13:36
  15. */
  16. public class ResponseTest {
  17. private static final String VAliD_REQUEST = "GET /index.html HTTP/1.1";
  18. private static final String INVAliD_REQUEST = "GET /notfound.html HTTP/1.1";
  19. private static final String STATUS200 = "HTTP/1.1 200 OK\r\n\r\n";
  20. private static final String STATUS404 = "HTTP/1.1 404 File Not Found\r\n\r\n";
  21. @Test
  22. public void givenValidRequest_thenReturnStaticResource() throws IOException {
  23. Request request = TestUtils.createRequest(VAliD_REQUEST);
  24. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  25. Response response = new Response(outputStream);
  26. response.setRequest(request);
  27. response.sendStaticResource();
  28. Assertions.assertEquals(
  29. (STATUS200 + TestUtils.readFileToString(ConnectorUtils.WEB_ROOT + request.getRequestUri())),
  30. outputStream.toString()
  31. );
  32. }
  33. @Test
  34. public void givenInvalidRequest_thenReturnError() throws IOException {
  35. Request request = TestUtils.createRequest(INVAliD_REQUEST);
  36. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  37. Response response = new Response(outputStream);
  38. response.setRequest(request);
  39. response.sendStaticResource();
  40. String resource=TestUtils.readFileToString(ConnectorUtils.WEB_ROOT +"/404.html");
  41. Assertions.assertEquals(
  42. (STATUS404 + resource),
  43. outputStream.toString()
  44. );
  45. }
  46. }

三、实现Connector和Processor

Processor

处理用户发送的请求,把请求对应的Response准备好

  1. package processor;
  2. import connector.Request;
  3. import connector.Response;
  4. import java.io.IOException;
  5. /**
  6. * @ClassName:
  7. * @Description:
  8. * @author: hszjj
  9. * @date: 2019/11/26 14:15
  10. */
  11. public class StaticProcessor {
  12. public void process(Request request, Response response){
  13. try {
  14. response.sendStaticResource();
  15. } catch (IOException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }

Connector

  1. package connector;
  2. import processor.ServletProcessor;
  3. import processor.StaticProcessor;
  4. import java.io.Closeable;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.io.OutputStream;
  8. import java.net.ServerSocket;
  9. import java.net.Socket;
  10. public class Connector implements Runnable {
  11. private static final int DEFAULT_PORT=9999;
  12. private ServerSocket server;
  13. private int port;
  14. private void shutDown(Closeable...closeables){
  15. try {
  16. for (Closeable shut:closeables){
  17. if (shut!=null){
  18. shut.close();
  19. }
  20. }
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. public Connector(int port){
  26. this.port=port;
  27. }
  28. public Connector(){
  29. this(DEFAULT_PORT);
  30. }
  31. public void start(){
  32. new Thread(this).start();
  33. }
  34. @Override
  35. public void run() {
  36. try {
  37. server=new ServerSocket(port);
  38. System.out.println("服务器已启动,监听端口【"+port+"】");
  39. while (true){
  40. Socket socket=server.accept();
  41. InputStream input=socket.getInputStream();
  42. OutputStream output=socket.getOutputStream();
  43. Request request=new Request(input);
  44. request.parse();
  45. Response response=new Response(output);
  46. response.setRequest(request);
  47. if (request.getRequestUri().startsWith("/servlet/")){
  48. ServletProcessor processor=new ServletProcessor();
  49. processor.process(request,response);
  50. }else {
  51. StaticProcessor processor = new StaticProcessor();
  52. processor.process(request, response);
  53. }
  54. shutDown(socket);
  55. }
  56. } catch (IOException e) {
  57. e.printStackTrace();
  58. }
  59. }
  60. }

服务器主函数Bootstrap

  1. import connector.Connector;
  2. /**
  3. * @ClassName:
  4. * @Description:
  5. * @author: hszjj
  6. * @date: 2019/11/26 14:29
  7. */
  8. public class Bootstrap {
  9. public static void main(String[] args) {
  10. Connector connector=new Connector();
  11. connector.start();
  12. }
  13. }

四、测试服务器

启动客户端

  1. package test;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.OutputStream;
  5. import java.net.Socket;
  6. /**
  7. * @ClassName:
  8. * @Description:
  9. * @author: hszjj
  10. * @date: 2019/11/26 14:33
  11. */
  12. public class TestClient {
  13. public static void main(String[] args) {
  14. try {
  15. Socket socket=new Socket("localhost",9999);
  16. OutputStream outputStream=socket.getOutputStream();
  17. outputStream.write("GET /index.html HTTP/1.1".getBytes());
  18. socket.shutdownOutput();
  19. InputStream inputStream=socket.getInputStream();
  20. byte[] buffer=new byte[2048];
  21. int length=inputStream.read(buffer);
  22. StringBuilder response=new StringBuilder();
  23. for (int j=0;j<length;j++){
  24. response.append((char)buffer[j]);
  25. }
  26. System.out.println(response.toString());
  27. socket.shutdownInput();
  28. socket.close();
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }

结果

image.png

效果查看

image.png
image.png

五、实现ServletRequest和ServletResponse

如图使Request继承ServletRequest接口和重写所有方法。
image.png同理,对Response做出同样的修改继承自ServletResponse。
image.png
其中,大部分函数都仅仅用默认修改即可,不过要对Response中的getWriter进行改写,方便动态资源写入进行便捷:

  1. @Override
  2. public PrintWriter getWriter() throws IOException {
  3. //自动Flush
  4. PrintWriter writer=new PrintWriter(output,true);
  5. return writer;
  6. }

六、实现Servlet

动态资源的Servlet
public class TimeServlet implements Servlet
主要的Service函数如下:

  1. @Override
  2. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  3. PrintWriter writer=servletResponse.getWriter();
  4. writer.println(ConnectorUtils.renderStatus(HttpStatus.SC_OK));
  5. writer.println("what time is it now?");
  6. writer.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
  7. }