小言_互联网的博客

Java模拟实现HTTP服务器

325人阅读  评论(0)

目录

一,HTTP 协议的工作过程

二、HTTP协议格式

1,抓包分析搜狗主页

2,协议格式总结

三、版本V1

四、版本V2

1,创建 HttpRequest 类

2,创建 HttpResponse 类

3,创建 HttpServer 类

五、版本V3

1. 创建 HttpRequest 类

2,创建 HttpResponse 类

3,创建 HttpServer 类

4,insex.html


一,HTTP 协议的工作过程

二、HTTP协议格式

1,抓包分析搜狗主页

HTTP请求

首行: [方法] + [url] + [版本]

Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部 分结束 Body: 空行后面的内容都是Body.

Body允许为空字符串. 如果Body存在, 则在Header中会有 一个Content-Length属性来标识Body的长度

HTTP响应

首行: [版本号] + [状态码] + [状态码解释]

Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部 分结束

Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有 一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页 面内容就是在body中

2,协议格式总结

三、版本V1

        实现一个最简单的 HTTP 服务器.

        在这个版本中, 我们只是简单解析 GET 请求, 并根据请求的路径来构造出不同的响应.

        路径为 /200, 返回一个 "欢迎页面".

        路径为 /404, 返回一个 "没有找到" 的页面.

        路径为 /302, 重定向到其他的页面


  
  1. import java.io.*;
  2. import java.net.ServerSocket;
  3. import java.net.Socket;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. import java.util.concurrent.ExecutorService;
  7. import java.util.concurrent.Executors;
  8. public class HttpServerV1 {
  9. // HTTP 底层要基于 TCP 来实现. 需要按照 TCP 的基本格式来先进行开发.
  10. private ServerSocket serverSocket = null;
  11. public HttpServerV1 (int port) throws IOException {
  12. serverSocket = new ServerSocket(port);
  13. }
  14. public void start () throws IOException {
  15. System.out.println( "服务器启动");
  16. ExecutorService executorService = Executors.newCachedThreadPool();
  17. while ( true) {
  18. // 1. 获取连接
  19. Socket clientSocket = serverSocket.accept();
  20. // 2. 处理连接(使用短连接的方式实现)
  21. executorService.execute( new Runnable() {
  22. @Override
  23. public void run () {
  24. process(clientSocket);
  25. }
  26. });
  27. }
  28. }
  29. private void process (Socket clientSocket) {
  30. // 由于 HTTP 协议是文本协议, 所以仍然使用字符流来处理.
  31. try ( BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(clientSocket.getInputStream()));
  32. BufferedWriter bufferedWriter = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream()))) {
  33. // 下面的操作都要严格按照 HTTP 协议来进行操作.
  34. // 1. 读取请求并解析
  35. // a) 解析首行, 三个部分使用空格切分
  36. String firstLine = bufferedReader.readLine();
  37. String[] firstLineTokens = firstLine.split( " ");
  38. String method = firstLineTokens[ 0];
  39. String url = firstLineTokens[ 1];
  40. String version = firstLineTokens[ 2];
  41. // b) 解析 header, 按行读取, 然后按照冒号空格来分割键值对
  42. Map<String, String> headers = new HashMap<>();
  43. String line = "";
  44. // readLine 读取的一行内容, 是会自动去掉换行符的. 对于空行来说, 去掉了换行符, 就变成空字符串
  45. while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
  46. // 不能使用 : 来切分. 像 referer 字段, 里面的内容是可能包含 : .
  47. String[] headerTokens = line.split( ": ");
  48. headers.put(headerTokens[ 0], headerTokens[ 1]);
  49. }
  50. // c) 解析 body (暂时先不考虑)
  51. // 请求解析完毕, 加上一个日志, 观察请求的内容是否正确.
  52. System.out.printf( "%s %s %s\n", method, url, version);
  53. for (Map.Entry<String, String> entry : headers.entrySet()) {
  54. System.out.println(entry.getKey() + ": " + entry.getValue());
  55. }
  56. System.out.println();
  57. // 2. 根据请求计算响应
  58. // 不管是啥样的请求, 咱们都返回一个 hello 这样的 html
  59. String resp = "";
  60. if (url.equals( "/200")) {
  61. bufferedWriter.write(version + " 200 OK\n");
  62. resp = "<h1>hello</h1>";
  63. } else if (url.equals( "/404")) {
  64. bufferedWriter.write(version + " 404 Not Found\n");
  65. resp = "<h1>not found</h1>";
  66. } else if (url.equals( "/302")) {
  67. bufferedWriter.write(version + " 303 Found\n");
  68. bufferedWriter.write( "Location: http://www.sogou.com\n");
  69. resp = "";
  70. } else {
  71. bufferedWriter.write(version + " 200 OK\n");
  72. resp = "<h1>default</h1>";
  73. }
  74. // 3. 把响应写回到客户端
  75. bufferedWriter.write( "Content-Type: text/html\n");
  76. bufferedWriter.write( "Content-Length: " + resp.getBytes().length + "\n"); // 此处的长度, 不能写成 resp.length(), 得到的是字符的数目, 而不是字节的数目
  77. bufferedWriter.write( "\n");
  78. bufferedWriter.write(resp);
  79. // 此处这个 flush 就算没有, 问题也不大. 紧接着
  80. // bufferedWriter 对象就要被关闭了. close 时就会自动触发刷新操作.
  81. bufferedWriter.flush();
  82. } catch (IOException e) {
  83. e.printStackTrace();
  84. } finally {
  85. try {
  86. clientSocket.close();
  87. } catch (IOException e) {
  88. e.printStackTrace();
  89. }
  90. }
  91. }
  92. public static void main (String[] args) throws IOException {
  93. HttpServerV1 server = new HttpServerV1( 9090);
  94. server.start();
  95. }
  96. }

 

四、版本V2

        在这个版本中, 我们只是简单解析 GET 请求, 并根据请求的路径来构造出不同的响应.

         在版本1 的基础上, 我们做出一些改进:

        把解析请求和构造响应的代码提取成单独的类.

        能够把 URL 中的 query string 解析成键值对.

        能够给浏览器返回 Cookie.

1,创建 HttpRequest 类

        对照着 HTTP 请求的格式, 创建属性: method, url, version, headers.

        创建 patameters, 用于存放 query string 的解析结果.

        创建一个静态方法 build, 用来完成解析 HTTP 请求的过程.

        从 socket 中读取数据的时候注意设置字符编码方式

        创建一系列 getter 方法获取到请求中的属性.

        单独写一个方法 parseKV 用来解析 query string


  
  1. import java.io.BufferedReader;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.InputStreamReader;
  5. import java.util.HashMap;
  6. import java.util.Map;
  7. // 表示一个 HTTP 请求, 并负责解析.
  8. public class HttpRequest {
  9. private String method;
  10. // /index.html?a=10&b=20
  11. private String url;
  12. private String version;
  13. private Map<String, String> headers = new HashMap<>();
  14. private Map<String, String> parameters = new HashMap<>();
  15. // 请求的构造逻辑, 也使用工厂模式来构造.
  16. // 此处的参数, 就是从 socket 中获取到的 InputStream 对象
  17. // 这个过程本质上就是在 "反序列化"
  18. public static HttpRequest build (InputStream inputStream) throws IOException {
  19. HttpRequest request = new HttpRequest();
  20. // 此处的逻辑中, 不能把 bufferedReader 写到 try ( ) 中.
  21. // 一旦写进去之后意味着 bufferReader 就会被关闭, 会影响到 clientSocket 的状态.
  22. // 等到最后整个请求处理完了, 再统一关闭
  23. BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(inputStream));
  24. // 此处的 build 的过程就是解析请求的过程.
  25. // 1. 解析首行
  26. String firstLine = bufferedReader.readLine();
  27. String[] firstLineTokens = firstLine.split( " ");
  28. request.method = firstLineTokens[ 0];
  29. request.url = firstLineTokens[ 1];
  30. request.version = firstLineTokens[ 2];
  31. // 2. 解析 url 中的参数
  32. int pos = request.url.indexOf( "?");
  33. if (pos != - 1) {
  34. // 看看 url 中是否有 ? . 如果没有, 就说明不带参数, 也就不必解析了
  35. // 此处的 parameters 是希望包含整个 参数 部分的内容
  36. // pos 表示 ? 的下标
  37. // /index.html?a=10&b=20
  38. // parameters 的结果就相当于是 a=10&b=20
  39. String parameters = request.url.substring(pos + 1);
  40. // 切分的最终结果, key a, value 10; key b, value 20;
  41. parseKV(parameters, request.parameters);
  42. }
  43. // 3. 解析 header
  44. String line = "";
  45. while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
  46. String[] headerTokens = line.split( ": ");
  47. request.headers.put(headerTokens[ 0], headerTokens[ 1]);
  48. }
  49. // 4. 解析 body (暂时先不考虑)
  50. return request;
  51. }
  52. private static void parseKV (String input, Map<String, String> output) {
  53. // 1. 先按照 & 切分成若干组键值对
  54. String[] kvTokens = input.split( "&");
  55. // 2. 针对切分结果再分别进行按照 = 切分, 就得到了键和值
  56. for (String kv : kvTokens) {
  57. String[] result = kv.split( "=");
  58. output.put(result[ 0], result[ 1]);
  59. }
  60. }
  61. // 给这个类构造一些 getter 方法. (不要搞 setter).
  62. // 请求对象的内容应该是从网络上解析来的. 用户不应该修改.
  63. public String getMethod () {
  64. return method;
  65. }
  66. public String getUrl () {
  67. return url;
  68. }
  69. public String getVersion () {
  70. return version;
  71. }
  72. // 此处的 getter 手动写, 自动生成的版本是直接得到整个 hash 表.
  73. // 而我们需要的是根据 key 来获取值.
  74. public String getHeader (String key) {
  75. return headers.get(key);
  76. }
  77. public String getParameter (String key) {
  78. return parameters.get(key);
  79. }
  80. @Override
  81. public String toString () {
  82. return "HttpRequest{" +
  83. "method='" + method + '\'' +
  84. ", url='" + url + '\'' +
  85. ", version='" + version + '\'' +
  86. ", headers=" + headers +
  87. ", parameters=" + parameters +
  88. '}';
  89. }
  90. }

2,创建 HttpResponse 类

        根据 HTTP 响应, 创建属性: version, status, message, headers, body

        另外创建一个 OutputStream, 用来关联到 Socket 的 OutputStream.

        往 socket 中写入数据的时候注意指定字符编码方式.

        创建一个静态方法 build, 用来构造 HttpResponse 对象.

        创建一系列 setter 方法, 用来设置 HttpResponse 的属性.

        创建一个 flush 方法, 用于最终把数据写入 OutputStream


  
  1. import java.io.BufferedWriter;
  2. import java.io.IOException;
  3. import java.io.OutputStream;
  4. import java.io.OutputStreamWriter;
  5. import java.util.HashMap;
  6. import java.util.Map;
  7. // 表示一个 HTTP 响应, 负责构造
  8. // 进行序列化操作
  9. public class HttpResponse {
  10. private String version = "HTTP/1.1";
  11. private int status; // 状态码
  12. private String message; // 状态码的描述信息
  13. private Map<String, String> headers = new HashMap<>();
  14. private StringBuilder body = new StringBuilder(); // 方便一会进行拼接.
  15. // 当代码需要把响应写回给客户端的时候, 就往这个 OutputStream 中写就好了.
  16. private OutputStream outputStream = null;
  17. public static HttpResponse build (OutputStream outputStream) {
  18. HttpResponse response = new HttpResponse();
  19. response.outputStream = outputStream;
  20. // 除了 outputStream 之外, 其他的属性的内容, 暂时都无法确定. 要根据代码的具体业务逻辑
  21. // 来确定. (服务器的 "根据请求并计算响应" 阶段来进行设置的)
  22. return response;
  23. }
  24. public void setVersion (String version) {
  25. this.version = version;
  26. }
  27. public void setStatus (int status) {
  28. this.status = status;
  29. }
  30. public void setMessage (String message) {
  31. this.message = message;
  32. }
  33. public void setHeader (String key, String value) {
  34. headers.put(key, value);
  35. }
  36. public void writeBody (String content) {
  37. body.append(content);
  38. }
  39. // 以上的设置属性的操作都是在内存中倒腾.
  40. // 还需要一个专门的方法, 把这些属性 按照 HTTP 协议 都写到 socket 中.
  41. public void flush () throws IOException {
  42. BufferedWriter bufferedWriter = new BufferedWriter( new OutputStreamWriter(outputStream));
  43. bufferedWriter.write(version + " " + status + " " + message + "\n");
  44. headers.put( "Content-Length", body.toString().getBytes().length + "");
  45. for (Map.Entry<String, String> entry : headers.entrySet()) {
  46. bufferedWriter.write(entry.getKey() + ": " + entry.getValue() + "\n");
  47. }
  48. bufferedWriter.write( "\n");
  49. bufferedWriter.write(body.toString());
  50. bufferedWriter.flush();
  51. }
  52. }

3,创建 HttpServer 类


  
  1. import java.io.IOException;
  2. import java.net.ServerSocket;
  3. import java.net.Socket;
  4. import java.util.concurrent.ExecutorService;
  5. import java.util.concurrent.Executors;
  6. public class HttpServerV2 {
  7. private ServerSocket serverSocket = null;
  8. public HttpServerV2 (int port) throws IOException {
  9. serverSocket = new ServerSocket(port);
  10. }
  11. public void start () throws IOException {
  12. System.out.println( "服务器启动");
  13. ExecutorService executorService = Executors.newCachedThreadPool();
  14. while ( true) {
  15. Socket clientSocket = serverSocket.accept();
  16. executorService.execute( new Runnable() {
  17. @Override
  18. public void run () {
  19. process(clientSocket);
  20. }
  21. });
  22. }
  23. }
  24. public void process (Socket clientSocket) {
  25. try {
  26. // 1. 读取并解析请求
  27. HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
  28. System.out.println( "request: " + request);
  29. HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
  30. response.setHeader( "Content-Type", "text/html");
  31. // 2. 根据请求计算响应
  32. if (request.getUrl().startsWith( "/200")) {
  33. response.setStatus( 200);
  34. response.setMessage( "OK");
  35. response.writeBody( "<h1>hello</h1>");
  36. } else if (request.getUrl().startsWith( "/add")) {
  37. // 这个逻辑要根据参数的内容进行计算
  38. // 先获取到 a 和 b 两个参数的值
  39. String aStr = request.getParameter( "a");
  40. String bStr = request.getParameter( "b");
  41. // System.out.println("a: " + aStr + ", b: " + bStr);
  42. int a = Integer.parseInt(aStr);
  43. int b = Integer.parseInt(bStr);
  44. int result = a + b;
  45. response.setStatus( 200);
  46. response.setMessage( "OK");
  47. response.writeBody( "<h1> result = " + result + "</h1>");
  48. } else if (request.getUrl().startsWith( "/cookieUser")) {
  49. response.setStatus( 200);
  50. response.setMessage( "OK");
  51. // HTTP 的 header 中允许有多个 Set-Cookie 字段. 但是
  52. // 此处 response 中使用 HashMap 来表示 header 的. 此时相同的 key 就覆盖
  53. response.setHeader( "Set-Cookie", "user=zhangsan");
  54. response.writeBody( "<h1>set cookieUser</h1>");
  55. } else if (request.getUrl().startsWith( "/cookieTime")) {
  56. response.setStatus( 200);
  57. response.setMessage( "OK");
  58. // HTTP 的 header 中允许有多个 Set-Cookie 字段. 但是
  59. // 此处 response 中使用 HashMap 来表示 header 的. 此时相同的 key 就覆盖
  60. response.setHeader( "Set-Cookie", "time=" + (System.currentTimeMillis() / 1000));
  61. response.writeBody( "<h1>set cookieTime</h1>");
  62. } else {
  63. response.setStatus( 200);
  64. response.setMessage( "OK");
  65. response.writeBody( "<h1>default</h1>");
  66. }
  67. // 3. 把响应写回到客户端
  68. response.flush();
  69. } catch (IOException | NullPointerException e) {
  70. e.printStackTrace();
  71. } finally {
  72. try {
  73. // 这个操作会同时关闭 getInputStream 和 getOutputStream 对象
  74. clientSocket.close();
  75. } catch (IOException e) {
  76. e.printStackTrace();
  77. }
  78. }
  79. }
  80. public static void main (String[] args) throws IOException {
  81. HttpServerV2 server = new HttpServerV2( 9090);
  82. server.start();
  83. }
  84. }

 

 

强化理解Cookie

Cookie就是一个字符串(里面的内容由程序员自己决定)

Cookie从服务器来,服务器会在header中引入一个Set-Cookie字段,对应的值就会保存在浏览器中

Cookie按照域名/地址来存,每个域名/地址有自己的Cookie

Cookei在后续访问相同的域名/地址的请求,就会自动带上Cookie,服务器感知到这个Cookie之后就可以在服务器端进行一些逻辑处理

五、版本V3

在版本 2 的基础上, 再做出进一步的改进.

解析请求中的 Cookie, 解析成键值对

解析请求中的 body, 按照 x-www-form-urlencoded 的方式解析.

根据请求方法, 分别调用 doGet / doPost

能够返回指定的静态页面. 实现简单的会话机制.

1. 创建 HttpRequest 类

属性中新增了 cookies 和 body

新增一个方法 parseCookie, 在解析 header 完成后解析

cookie 新增了解析 body 的流程.


  
  1. import javax.print.attribute.standard.RequestingUserName;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.InputStreamReader;
  6. import java.util.HashMap;
  7. import java.util.Map;
  8. public class HttpRequest {
  9. private String method;
  10. private String url;
  11. private String version;
  12. private Map<String, String> headers = new HashMap<>();
  13. // url 中的参数和 body 中的参数都放到这个 parameters hash 表中.
  14. private Map<String, String> parameters = new HashMap<>();
  15. private Map<String, String> cookies = new HashMap<>();
  16. private String body;
  17. public static HttpRequest build (InputStream inputStream) throws IOException {
  18. HttpRequest request = new HttpRequest();
  19. BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(inputStream));
  20. // 1. 处理首行
  21. String firstLine = bufferedReader.readLine();
  22. String[] firstLineTokens = firstLine.split( " ");
  23. request.method = firstLineTokens[ 0];
  24. request.url = firstLineTokens[ 1];
  25. request.version = firstLineTokens[ 2];
  26. // 2. 解析 url
  27. int pos = request.url.indexOf( "?");
  28. if (pos != - 1) {
  29. String queryString = request.url.substring(pos + 1);
  30. parseKV(queryString, request.parameters);
  31. }
  32. // 3. 循环处理 header 部分
  33. String line = "";
  34. while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
  35. String[] headerTokens = line.split( ": ");
  36. request.headers.put(headerTokens[ 0], headerTokens[ 1]);
  37. }
  38. // 4. 解析 cookie
  39. String cookie = request.headers.get( "Cookie");
  40. if (cookie != null) {
  41. // 把 cookie 进行解析
  42. parseCookie(cookie, request.cookies);
  43. }
  44. // 5. 解析 body
  45. if ( "POST".equalsIgnoreCase(request.method)
  46. || "PUT".equalsIgnoreCase(request.method)) {
  47. // 这两个方法需要处理 body, 其他方法暂时不考虑
  48. // 需要把 body 读取出来.
  49. // 需要先知道 body 的长度. Content-Length 就是干这个的.
  50. // 此处的长度单位是 "字节"
  51. int contentLength = Integer.parseInt(request.headers.get( "Content-Length"));
  52. // 注意体会此处的含义~~
  53. // 例如 contentLength 为 100 , body 中有 100 个字节.
  54. // 下面创建的缓冲区长度是 100 个 char (相当于是 200 个字节)
  55. // 缓冲区不怕长. 就怕不够用. 这样创建的缓冲区才能保证长度管够~~
  56. char[] buffer = new char[contentLength];
  57. int len = bufferedReader.read(buffer);
  58. request.body = new String(buffer, 0, len);
  59. // body 中的格式形如: username=tanglaoshi&password=123
  60. parseKV(request.body, request.parameters);
  61. }
  62. return request;
  63. }
  64. private static void parseCookie (String cookie, Map<String, String> cookies) {
  65. // 1. 按照 分号空格 拆分成多个键值对
  66. String[] kvTokens = cookie.split( "; ");
  67. // 2. 按照 = 拆分每个键和值
  68. for (String kv : kvTokens) {
  69. String[] result = kv.split( "=");
  70. cookies.put(result[ 0], result[ 1]);
  71. }
  72. }
  73. private static void parseKV (String queryString, Map<String, String> parameters) {
  74. // 1. 按照 & 拆分成多个键值对
  75. String[] kvTokens = queryString.split( "&");
  76. // 2. 按照 = 拆分每个键和值
  77. for (String kv : kvTokens) {
  78. String[] result = kv.split( "=");
  79. parameters.put(result[ 0], result[ 1]);
  80. }
  81. }
  82. public String getMethod () {
  83. return method;
  84. }
  85. public String getUrl () {
  86. return url;
  87. }
  88. public String getVersion () {
  89. return version;
  90. }
  91. public String getBody () {
  92. return body;
  93. }
  94. public String getParameter (String key) {
  95. return parameters.get(key);
  96. }
  97. public String getHeader (String key) {
  98. return headers.get(key);
  99. }
  100. public String getCookie (String key) {
  101. return cookies.get(key);
  102. }
  103. }

2,创建 HttpResponse 类


  
  1. import java.io.BufferedWriter;
  2. import java.io.IOException;
  3. import java.io.OutputStream;
  4. import java.io.OutputStreamWriter;
  5. import java.util.HashMap;
  6. import java.util.Map;
  7. public class HttpResponse {
  8. private String version = "HTTP/1.1";
  9. private int status;
  10. private String message;
  11. private Map<String, String> headers = new HashMap<>();
  12. private StringBuilder body = new StringBuilder();
  13. private OutputStream outputStream = null;
  14. public static HttpResponse build (OutputStream outputStream) {
  15. HttpResponse response = new HttpResponse();
  16. response.outputStream = outputStream;
  17. return response;
  18. }
  19. public void setVersion (String version) {
  20. this.version = version;
  21. }
  22. public void setStatus (int status) {
  23. this.status = status;
  24. }
  25. public void setMessage (String message) {
  26. this.message = message;
  27. }
  28. public void setHeader (String key, String value) {
  29. headers.put(key, value);
  30. }
  31. public void writeBody (String content) {
  32. body.append(content);
  33. }
  34. public void flush () throws IOException {
  35. BufferedWriter bufferedWriter = new BufferedWriter( new OutputStreamWriter(outputStream));
  36. bufferedWriter.write(version + " " + status + " " + message + "\n");
  37. headers.put( "Content-Length", body.toString().getBytes().length + "");
  38. for (Map.Entry<String, String> entry : headers.entrySet()) {
  39. bufferedWriter.write(entry.getKey() + ": " + entry.getValue() + "\n");
  40. }
  41. bufferedWriter.write( "\n");
  42. bufferedWriter.write(body.toString());
  43. bufferedWriter.flush();
  44. }
  45. }

3,创建 HttpServer 类


  
  1. import java.io.*;
  2. import java.net.ServerSocket;
  3. import java.net.Socket;
  4. import java.util.HashMap;
  5. import java.util.UUID;
  6. import java.util.concurrent.ExecutorService;
  7. import java.util.concurrent.Executors;
  8. public class HttpServerV3 {
  9. static class User {
  10. // 保存用户的相关信息
  11. public String userName;
  12. public int age;
  13. public String school;
  14. }
  15. private ServerSocket serverSocket = null;
  16. // session 会话. 指的就是同一个用户的一组访问服务器的操作, 归类到一起, 就是一个会话.
  17. // 记者来采访你, 记者问的问题就是一个请求, 你回答的内容, 就是一个响应. 一次采访过程中
  18. // 涉及到很多问题和回答(请求和响应), 这一组问题和回答, 就可以称为是一个 "会话" (整个采访的过程)
  19. // sessions 中就包含很多会话. (每个键值对就是一个会话)
  20. private HashMap<String, User> sessions = new HashMap<>();
  21. public HttpServerV3 (int port) throws IOException {
  22. serverSocket = new ServerSocket(port);
  23. }
  24. public void start () throws IOException {
  25. System.out.println( "服务器启动");
  26. ExecutorService executorService = Executors.newCachedThreadPool();
  27. while ( true) {
  28. Socket clientSocket = serverSocket.accept();
  29. executorService.execute( new Runnable() {
  30. @Override
  31. public void run () {
  32. process(clientSocket);
  33. }
  34. });
  35. }
  36. }
  37. public void process (Socket clientSocket) {
  38. // 处理核心逻辑
  39. try {
  40. // 1. 读取请求并解析
  41. HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
  42. HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
  43. // 2. 根据请求计算响应
  44. // 此处按照不同的 HTTP 方法, 拆分成多个不同的逻辑
  45. if ( "GET".equalsIgnoreCase(request.getMethod())) {
  46. doGet(request, response);
  47. } else if ( "POST".equalsIgnoreCase(request.getMethod())) {
  48. doPost(request, response);
  49. } else {
  50. // 其他方法, 返回一个 405 这样的状态码
  51. response.setStatus( 405);
  52. response.setMessage( "Method Not Allowed");
  53. }
  54. // 3. 把响应写回到客户端
  55. response.flush();
  56. } catch (IOException | NullPointerException e) {
  57. e.printStackTrace();
  58. } finally {
  59. try {
  60. clientSocket.close();
  61. } catch (IOException e) {
  62. e.printStackTrace();
  63. }
  64. }
  65. }
  66. private void doGet (HttpRequest request, HttpResponse response) throws IOException {
  67. // 1. 能够支持返回一个 html 文件.
  68. if (request.getUrl().startsWith( "/index.html")) {
  69. String sessionId = request.getCookie( "sessionId");
  70. User user = sessions.get(sessionId);
  71. if (sessionId == null || user == null) {
  72. // 说明当前用户尚未登陆, 就返回一个登陆页面即可.
  73. // 这种情况下, 就让代码读取一个 index.html 这样的文件.
  74. // 要想读文件, 需要先知道文件路径. 而现在只知道一个 文件名 index.html
  75. // 此时这个 html 文件所属的路径, 可以自己来约定(约定某个 d:/...) 专门放 html .
  76. // 把文件内容写入到响应的 body 中
  77. response.setStatus( 200);
  78. response.setMessage( "OK");
  79. response.setHeader( "Content-Type", "text/html; charset=utf-8");
  80. InputStream inputStream = HttpServerV3.class.getClassLoader().getResourceAsStream( "index.html");
  81. BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(inputStream));
  82. // 按行读取内容, 把数据写入到 response 中
  83. String line = null;
  84. while ((line = bufferedReader.readLine()) != null) {
  85. response.writeBody(line + "\n");
  86. }
  87. bufferedReader.close();
  88. } else {
  89. // 用户已经登陆, 无需再登陆了.
  90. response.setStatus( 200);
  91. response.setMessage( "OK");
  92. response.setHeader( "Content-Type", "text/html; charset=utf-8");
  93. response.writeBody( "<html>");
  94. response.writeBody( "<div>" + "您已经登陆了! 无需再次登陆! 用户名: " + user.userName + "</div>");
  95. response.writeBody(+ user.age + "</div>");
  96. response.writeBody( "<div>" + user.school + "</div>");
  97. response.writeBody( "</html>");
  98. }
  99. }
  100. }
  101. private void doPost (HttpRequest request, HttpResponse response) {
  102. // 2. 实现 /login 的处理
  103. if (request.getUrl().startsWith( "/login")) {
  104. // 读取用户提交的用户名和密码
  105. String userName = request.getParameter( "username");
  106. String password = request.getParameter( "password");
  107. // System.out.println("userName: " + userName);
  108. // System.out.println("password: " + password);
  109. // 登陆逻辑就需要验证用户名密码是否正确.
  110. // 此处为了简单, 咱们把用户名和密码在代码中写死了.
  111. // 更科学的处理方式, 应该是从数据库中读取用户名对应的密码, 校验密码是否一致.
  112. if ( "zhangsan".equals(userName) && "123".equals(password)) {
  113. // 登陆成功
  114. response.setStatus( 200);
  115. response.setMessage( "OK");
  116. response.setHeader( "Content-Type", "text/html; charset=utf-8");
  117. // 原来登陆成功, 是给浏览器写了一个 cookie, cookie 中保存的是用户的用户名.
  118. // response.setHeader("Set-Cookie", "userName=" + userName);
  119. // 现有的对于登陆成功的处理. 给这次登陆的用户分配了一个 session
  120. // (在 hash 中新增了一个键值对), key 是随机生成的. value 就是用户的身份信息
  121. // 身份信息保存在服务器中, 此时也就不再有泄露的问题了
  122. // 给浏览器返回的 Cookie 中只需要包含 sessionId 即可
  123. String sessionId = UUID.randomUUID().toString();
  124. User user = new User();
  125. user.userName = "zhangsan";
  126. user.age = 20;
  127. user.school = "北京大学";
  128. sessions.put(sessionId, user);
  129. response.setHeader( "Set-Cookie", "sessionId=" + sessionId);
  130. response.writeBody( "<html>");
  131. response.writeBody( "<div>欢迎您! " + userName + "</div>");
  132. response.writeBody( "</html>");
  133. } else {
  134. // 登陆失败
  135. response.setStatus( 403);
  136. response.setMessage( "Forbidden");
  137. response.setHeader( "Content-Type", "text/html; charset=utf-8");
  138. response.writeBody( "<html>");
  139. response.writeBody( "<div>登陆失败</div>");
  140. response.writeBody( "</html>");
  141. }
  142. }
  143. }
  144. public static void main (String[] args) throws IOException {
  145. HttpServerV3 serverV3 = new HttpServerV3( 9090);
  146. serverV3.start();
  147. }
  148. }

4,insex.html


  
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport"
  6. content= "width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  7. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  8. <title>登录页面 </title>
  9. </head>
  10. <body>
  11. <form method="post" action="/login">
  12. <div style="margin-bottom: 5px">
  13. <input type="text" name="username" placeholder="请输入名字">
  14. </div>
  15. <div style="margin-bottom: 5px">
  16. <input type="password" name="password" placeholder="请输入密码">
  17. </div>
  18. <div>
  19. <input type="submit" value="登录">
  20. </div>
  21. </form>
  22. </body>
  23. </html>

 请求中没有Cookie

  响应中带有Cookie字段,此时浏览器就会带有Cookie


转载:https://blog.csdn.net/qq_50156012/article/details/123373575
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场