JAVA简易WEB服务器(三)

淡淡的烟草味﹌ 2022-02-01 19:36 360阅读 0赞

在上一篇《JAVA简易WEB服务器(二)》中我们完成了对浏览器请求的解析,这一篇我们继续来实现响应浏览器的请求,同样的,我们还是先来看一下服务端响应给浏览器的数据格式

  1. HTTP/1.1 200 OK
  2. Server: Apache-Coyote/1.1
  3. Accept-Ranges: bytes
  4. ETag: W/"129-1456125361109"
  5. Last-Modified: Mon, 22 Feb 2016 07:16:01 GMT
  6. Content-Type: text/html
  7. Content-Length: 129
  8. Date: Mon, 22 Feb 2016 08:08:32 GMT
  9. <!DOCTYPE html>
  10. <html>
  11. <head>
  12. <meta charset="utf-8">
  13. <title>test</title>
  14. </head>
  15. <body>this is test page.
  16. </body>
  17. </html>

只要我们响应的数据满足这个格式,浏览器就可以正常解析了,在解析之前还是先来做一些准备工作。
相应请求时可能会出现异常,所以我们先编写一个异常类,和请求的异常类类似。

  1. package com.gujin.server;
  2. /**
  3. * 响应异常
  4. *
  5. * @author jianggujin
  6. *
  7. */
  8. public class HQResponseException extends RuntimeException
  9. {
  10. private static final long serialVersionUID = 1L;
  11. public HQResponseException()
  12. {
  13. super();
  14. }
  15. public HQResponseException(String message)
  16. {
  17. super(message);
  18. }
  19. public HQResponseException(String message, Throwable cause)
  20. {
  21. super(message, cause);
  22. }
  23. public HQResponseException(Throwable cause)
  24. {
  25. super(cause);
  26. }
  27. }

不管是请求还是响应都需要对字符串进行操作,所以将字符串操作的方法抽取出来形成一个字符串工具类。

  1. package com.gujin.server.utils;
  2. /**
  3. * 字符串工具
  4. *
  5. * @author jianggujin
  6. *
  7. */
  8. public class HQString
  9. {
  10. /**
  11. * 判断字符串为空
  12. *
  13. * @param s
  14. * @return
  15. */
  16. public static boolean isEmpty(String s)
  17. {
  18. return s == null || s.length() == 0;
  19. }
  20. /**
  21. * 判断字符串是否非空
  22. *
  23. * @param s
  24. * @return
  25. */
  26. public static boolean isNotEmpty(String s)
  27. {
  28. return !isEmpty(s);
  29. }
  30. /**
  31. * 判断字符串是空白字符
  32. *
  33. * @param s
  34. * @return
  35. */
  36. public static boolean isBlack(String s)
  37. {
  38. return isEmpty(s) || s.matches("\\s*");
  39. }
  40. /**
  41. * 判断字符串是非空白字符F
  42. *
  43. * @param s
  44. * @return
  45. */
  46. public static boolean isNotBlack(String s)
  47. {
  48. return !isBlack(s);
  49. }
  50. /**
  51. * 截取字符串
  52. *
  53. * @param s
  54. * @param flag
  55. * @return
  56. */
  57. public static String subStringBefore(String s, String flag)
  58. {
  59. if (isEmpty(s) || isEmpty(flag))
  60. {
  61. return s;
  62. }
  63. int index = s.indexOf(flag);
  64. if (index != -1)
  65. {
  66. return s.substring(0, index);
  67. }
  68. return flag;
  69. }
  70. /**
  71. * 截取字符串
  72. *
  73. * @param s
  74. * @param flag
  75. * @return
  76. */
  77. public static String subStringAfter(String s, String flag)
  78. {
  79. if (isEmpty(s) || isEmpty(flag))
  80. {
  81. return s;
  82. }
  83. int index = s.indexOf(flag);
  84. if (index != -1)
  85. {
  86. return s.substring(index + flag.length());
  87. }
  88. return flag;
  89. }
  90. }

好了,准备工作已经基本完成了,下面我们来编写响应类

  1. package com.gujin.server;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.IOException;
  4. import java.io.OutputStream;
  5. import java.net.Socket;
  6. import java.text.MessageFormat;
  7. import java.text.SimpleDateFormat;
  8. import java.util.Date;
  9. import java.util.Locale;
  10. import com.gujin.server.utils.HQString;
  11. /**
  12. * HTTP响应
  13. *
  14. * @author jianggujin
  15. *
  16. */
  17. public class HQResponse
  18. {
  19. /** 缓冲区大小 **/
  20. private final int BUFSIZE = 512;
  21. /** 响应时间格式化 **/
  22. private final String RESPONSE_DATE_TIME = "EEE, dd MMM yyyy HH:mm:ss 'GMT'";
  23. /** HTTP版本 **/
  24. private final String HTTP_VERSION = "HTTP/1.1";
  25. /** 响应时间格式化 **/
  26. private final SimpleDateFormat RESPONSE_DATE_FORMAT = new SimpleDateFormat(
  27. RESPONSE_DATE_TIME, Locale.US);
  28. /** 缓冲输出流 **/
  29. private ByteArrayOutputStream bufferStream = null;
  30. /** Socket输出流 **/
  31. private OutputStream stream = null;
  32. /** 响应码 **/
  33. private int statusCode = 200;
  34. /** 内容类型 **/
  35. private String contentType;
  36. /**
  37. * 构造方法
  38. *
  39. * @param socket
  40. * @throws IOException
  41. */
  42. public HQResponse(Socket socket) throws IOException
  43. {
  44. bufferStream = new ByteArrayOutputStream(BUFSIZE);
  45. stream = socket.getOutputStream();
  46. }
  47. /**
  48. * 向客户端写数据
  49. *
  50. * @param data
  51. * @throws IOException
  52. */
  53. public void write(byte[] data) throws IOException
  54. {
  55. bufferStream.write(data);
  56. }
  57. /**
  58. * 向客户端写数据
  59. *
  60. * @param data
  61. * @param start
  62. * @param len
  63. */
  64. public void write(byte[] data, int start, int len)
  65. {
  66. bufferStream.write(data, start, len);
  67. }
  68. /**
  69. * 向客户端发送头信息
  70. *
  71. * @throws IOException
  72. */
  73. private void writeHeader() throws IOException
  74. {
  75. stream.write(MessageFormat.format("{0} {1} {2}\r\n", HTTP_VERSION,
  76. statusCode, "OK").getBytes());
  77. stream.write(MessageFormat.format("Date: {0}\r\n",
  78. RESPONSE_DATE_FORMAT.format(new Date())).getBytes());
  79. stream.write("Server: HQHttpServer 1.0".getBytes());
  80. if (HQString.isNotEmpty(contentType))
  81. {
  82. stream.write(MessageFormat
  83. .format("Content-Type: {0}\r\n", contentType).getBytes());
  84. }
  85. stream.write(MessageFormat.format("Content-Length: {0}\r\n",
  86. bufferStream.size()).getBytes());
  87. }
  88. /**
  89. * 实际响应
  90. *
  91. * @throws IOException
  92. */
  93. public void response() throws IOException
  94. {
  95. writeHeader();
  96. // 换一行
  97. stream.write("\r\n".getBytes());
  98. bufferStream.writeTo(stream);
  99. bufferStream.flush();
  100. stream.flush();
  101. }
  102. /**
  103. * 获得内容类型
  104. *
  105. * @return
  106. */
  107. public String getContentType()
  108. {
  109. return contentType;
  110. }
  111. /**
  112. * 设置内容类型
  113. *
  114. * @param contentType
  115. */
  116. public void setContentType(String contentType)
  117. {
  118. this.contentType = contentType;
  119. }
  120. }

最后我们对上一篇博客中的HQHttpServer类中的handleRequest方法进行改造,将浏览器请求的头信息响应给浏览器,完成一次交互,完整代码如下:

  1. package com.gujin.server;
  2. import java.io.IOException;
  3. import java.net.ServerSocket;
  4. import java.net.Socket;
  5. import java.text.MessageFormat;
  6. import java.util.Iterator;
  7. import java.util.logging.Level;
  8. import com.gujin.server.utils.HQClose;
  9. /**
  10. * 服务端
  11. *
  12. * @author jianggujin
  13. *
  14. */
  15. public class HQHttpServer implements HQHttpServerLog
  16. {
  17. /** 端口号 **/
  18. private int port = 80;
  19. /** 服务套接字 **/
  20. private ServerSocket serverSocket = null;
  21. /**
  22. * 默认构造方法
  23. */
  24. public HQHttpServer()
  25. {
  26. }
  27. /**
  28. * 构造方法
  29. *
  30. * @param port
  31. */
  32. public HQHttpServer(int port)
  33. {
  34. this.port = port;
  35. }
  36. /**
  37. * 启动服务器
  38. */
  39. public synchronized void start()
  40. {
  41. try
  42. {
  43. serverSocket = new ServerSocket(port);
  44. LOG.info("server init success.");
  45. }
  46. catch (IOException e)
  47. {
  48. LOG.log(Level.SEVERE, e.getMessage(), e);
  49. }
  50. new Thread()
  51. {
  52. public void run()
  53. {
  54. while (!isStop())
  55. {
  56. Socket socket;
  57. try
  58. {
  59. socket = serverSocket.accept();
  60. handleRequest(socket);
  61. }
  62. catch (IOException e)
  63. {
  64. LOG.log(Level.SEVERE, e.getMessage(), e);
  65. }
  66. }
  67. };
  68. }.start();
  69. }
  70. /**
  71. * 处理请求
  72. *
  73. * @param socket
  74. * @throws IOException
  75. */
  76. public void handleRequest(Socket socket) throws IOException
  77. {
  78. HQRequest request = new HQRequest(socket);
  79. request.execute();
  80. HQResponse response = new HQResponse(socket);
  81. response.setContentType("text/plain");
  82. Iterator<String> iterator = request.getHeaderNames();
  83. while (iterator.hasNext())
  84. {
  85. String name = iterator.next();
  86. response.write(MessageFormat.format("{0}: {1}", name,
  87. request.getHeader(name)).getBytes());
  88. }
  89. response.response();
  90. socket.close();
  91. }
  92. /**
  93. * 是否停止
  94. *
  95. * @return
  96. */
  97. public boolean isStop()
  98. {
  99. return serverSocket == null || serverSocket.isClosed();
  100. }
  101. /**
  102. * 停止服务器
  103. */
  104. public synchronized void stop()
  105. {
  106. if (!isStop())
  107. {
  108. HQClose.safeClose(serverSocket);
  109. serverSocket = null;
  110. }
  111. }
  112. public static void main(String[] args)
  113. {
  114. new HQHttpServer().start();
  115. }
  116. }

运行程序,在浏览器输入:http://127.0.0.1,我们可以看到浏览器已经可以正常的接收服务端相应的数据了,页面显示内容如下:
ACCEPT-ENCODING: gzip,deflate,sdchHOST: 127.0.0.1USER-AGENT: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36CONNECTION: keep-aliveACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8ACCEPT-LANGUAGE: zh-CN,zh;q=0.8

打开浏览器的开发者工具观察服务端响应的数据信息
这里写图片描述

我们可以看到浏览器接收到的头信心与我们相应的数据是一致的。
到这里,一个最简单的WEB服务器已经完成了与浏览器的一次完成整的交互,接下来我们要继续对其优化。

发表评论

表情:
评论列表 (有 0 条评论,360人围观)

还没有评论,来说两句吧...

相关阅读