Java模拟实现HTTP服务器项目实战

目录
  • 一,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, 重定向到其他的页面

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServerV1 {
    // HTTP 底层要基于 TCP 来实现. 需要按照 TCP 的基本格式来先进行开发.
    private ServerSocket serverSocket = null;

    public HttpServerV1(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true) {
            // 1. 获取连接
            Socket clientSocket = serverSocket.accept();
            // 2. 处理连接(使用短连接的方式实现)
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    process(clientSocket);
                }
            });
        }
    }

    private void process(Socket clientSocket) {
        // 由于 HTTP 协议是文本协议, 所以仍然使用字符流来处理.
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
            // 下面的操作都要严格按照 HTTP 协议来进行操作.
            // 1. 读取请求并解析
            //  a) 解析首行, 三个部分使用空格切分
            String firstLine = bufferedReader.readLine();
            String[] firstLineTokens = firstLine.split(" ");
            String method = firstLineTokens[0];
            String url = firstLineTokens[1];
            String version = firstLineTokens[2];
            //  b) 解析 header, 按行读取, 然后按照冒号空格来分割键值对
            Map<String, String> headers = new HashMap<>();
            String line = "";
            // readLine 读取的一行内容, 是会自动去掉换行符的. 对于空行来说, 去掉了换行符, 就变成空字符串
            while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
                // 不能使用 : 来切分. 像 referer 字段, 里面的内容是可能包含 : .
                String[] headerTokens = line.split(": ");
                headers.put(headerTokens[0], headerTokens[1]);
            }
            //  c) 解析 body (暂时先不考虑)
            // 请求解析完毕, 加上一个日志, 观察请求的内容是否正确.
            System.out.printf("%s %s %s\n", method, url, version);
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                System.out.println(entry.getKey() + ": " + entry.getValue());
            }
            System.out.println();
            // 2. 根据请求计算响应
            // 不管是啥样的请求, 咱们都返回一个 hello 这样的 html
            String resp = "";
            if (url.equals("/200")) {
                bufferedWriter.write(version + " 200 OK\n");
                resp = "<h1>hello</h1>";
            } else if (url.equals("/404")) {
                bufferedWriter.write(version + " 404 Not Found\n");
                resp = "<h1>not found</h1>";
            } else if (url.equals("/302")) {
                bufferedWriter.write(version + " 303 Found\n");
                bufferedWriter.write("Location: http://www.sogou.com\n");
                resp = "";
            } else {
                bufferedWriter.write(version + " 200 OK\n");
                resp = "<h1>default</h1>";
            }
            // 3. 把响应写回到客户端
            bufferedWriter.write("Content-Type: text/html\n");
            bufferedWriter.write("Content-Length: " + resp.getBytes().length + "\n"); // 此处的长度, 不能写成 resp.length(), 得到的是字符的数目, 而不是字节的数目
            bufferedWriter.write("\n");
            bufferedWriter.write(resp);
            // 此处这个 flush 就算没有, 问题也不大. 紧接着
            // bufferedWriter 对象就要被关闭了. close 时就会自动触发刷新操作.
            bufferedWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        HttpServerV1 server = new HttpServerV1(9090);
        server.start();
    }
}

四、版本V2

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

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

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

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

能够给浏览器返回 Cookie.

1,创建 HttpRequest 类

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

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

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

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

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

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

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

// 表示一个 HTTP 请求, 并负责解析.
public class HttpRequest {
    private String method;
    // /index.html?a=10&b=20
    private String url;
    private String version;
    private Map<String, String> headers = new HashMap<>();
    private Map<String, String> parameters = new HashMap<>();

    // 请求的构造逻辑, 也使用工厂模式来构造.
    // 此处的参数, 就是从 socket 中获取到的 InputStream 对象
    // 这个过程本质上就是在 "反序列化"
    public static HttpRequest build(InputStream inputStream) throws IOException {
        HttpRequest request = new HttpRequest();
        // 此处的逻辑中, 不能把 bufferedReader 写到 try ( ) 中.
        // 一旦写进去之后意味着 bufferReader 就会被关闭, 会影响到 clientSocket 的状态.
        // 等到最后整个请求处理完了, 再统一关闭
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        // 此处的 build 的过程就是解析请求的过程.
        // 1. 解析首行
        String firstLine = bufferedReader.readLine();
        String[] firstLineTokens = firstLine.split(" ");
        request.method = firstLineTokens[0];
        request.url = firstLineTokens[1];
        request.version = firstLineTokens[2];
        // 2. 解析 url 中的参数
        int pos = request.url.indexOf("?");
        if (pos != -1) {
            // 看看 url 中是否有 ? . 如果没有, 就说明不带参数, 也就不必解析了
            // 此处的 parameters 是希望包含整个 参数 部分的内容
            // pos 表示 ? 的下标
            // /index.html?a=10&b=20
            // parameters 的结果就相当于是 a=10&b=20
            String parameters = request.url.substring(pos + 1);
            // 切分的最终结果, key a, value 10; key b, value 20;
            parseKV(parameters, request.parameters);
        }
        // 3. 解析 header
        String line = "";
        while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
            String[] headerTokens = line.split(": ");
            request.headers.put(headerTokens[0], headerTokens[1]);
        }
        // 4. 解析 body (暂时先不考虑)
        return request;
    }

    private static void parseKV(String input, Map<String, String> output) {
        // 1. 先按照 & 切分成若干组键值对
        String[] kvTokens = input.split("&");
        // 2. 针对切分结果再分别进行按照 = 切分, 就得到了键和值
        for (String kv : kvTokens) {
            String[] result = kv.split("=");
            output.put(result[0], result[1]);
        }
    }

    // 给这个类构造一些 getter 方法. (不要搞 setter).
    // 请求对象的内容应该是从网络上解析来的. 用户不应该修改.
    public String getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }

    public String getVersion() {
        return version;
    }

    // 此处的 getter 手动写, 自动生成的版本是直接得到整个 hash 表.
    // 而我们需要的是根据 key 来获取值.
    public String getHeader(String key) {
        return headers.get(key);
    }

    public String getParameter(String key) {
        return parameters.get(key);
    }

    @Override
    public String toString() {
        return "HttpRequest{" +
                "method='" + method + '\'' +
                ", url='" + url + '\'' +
                ", version='" + version + '\'' +
                ", headers=" + headers +
                ", parameters=" + parameters +
                '}';
    }
}

2,创建 HttpResponse 类

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

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

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

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

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

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

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;

// 表示一个 HTTP 响应, 负责构造
// 进行序列化操作
public class HttpResponse {
    private String version = "HTTP/1.1";
    private int status;     // 状态码
    private String message; // 状态码的描述信息
    private Map<String, String> headers = new HashMap<>();
    private StringBuilder body = new StringBuilder(); // 方便一会进行拼接.
    // 当代码需要把响应写回给客户端的时候, 就往这个 OutputStream 中写就好了.
    private OutputStream outputStream = null;

    public static HttpResponse build(OutputStream outputStream) {
        HttpResponse response = new HttpResponse();
        response.outputStream = outputStream;
        // 除了 outputStream 之外, 其他的属性的内容, 暂时都无法确定. 要根据代码的具体业务逻辑
        // 来确定. (服务器的 "根据请求并计算响应" 阶段来进行设置的)
        return response;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void setHeader(String key, String value) {
        headers.put(key, value);
    }

    public void writeBody(String content) {
        body.append(content);
    }

    // 以上的设置属性的操作都是在内存中倒腾.
    // 还需要一个专门的方法, 把这些属性 按照 HTTP 协议 都写到 socket 中.
    public void flush() throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write(version + " " + status + " " + message + "\n");
        headers.put("Content-Length", body.toString().getBytes().length + "");
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            bufferedWriter.write(entry.getKey() + ": " + entry.getValue() + "\n");
        }
        bufferedWriter.write("\n");
        bufferedWriter.write(body.toString());
        bufferedWriter.flush();
    }
}

3,创建 HttpServer 类

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServerV2 {
    private ServerSocket serverSocket = null;

    public HttpServerV2(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true) {
            Socket clientSocket = serverSocket.accept();
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    process(clientSocket);
                }
            });
        }
    }

    public void process(Socket clientSocket) {
        try {
            // 1. 读取并解析请求
            HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
            System.out.println("request: " + request);
            HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
            response.setHeader("Content-Type", "text/html");
            // 2. 根据请求计算响应
            if (request.getUrl().startsWith("/200")) {
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>hello</h1>");
            } else if (request.getUrl().startsWith("/add")) {
                // 这个逻辑要根据参数的内容进行计算
                // 先获取到 a 和 b 两个参数的值
                String aStr = request.getParameter("a");
                String bStr = request.getParameter("b");
                // System.out.println("a: " + aStr + ", b: " + bStr);
                int a = Integer.parseInt(aStr);
                int b = Integer.parseInt(bStr);
                int result = a + b;
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1> result = " + result + "</h1>");
            } else if (request.getUrl().startsWith("/cookieUser")) {
                response.setStatus(200);
                response.setMessage("OK");
                // HTTP 的 header 中允许有多个 Set-Cookie 字段. 但是
                // 此处 response 中使用 HashMap 来表示 header 的. 此时相同的 key 就覆盖
                response.setHeader("Set-Cookie", "user=zhangsan");
                response.writeBody("<h1>set cookieUser</h1>");
            } else if (request.getUrl().startsWith("/cookieTime")) {
                response.setStatus(200);
                response.setMessage("OK");
                // HTTP 的 header 中允许有多个 Set-Cookie 字段. 但是
                // 此处 response 中使用 HashMap 来表示 header 的. 此时相同的 key 就覆盖
                response.setHeader("Set-Cookie", "time=" + (System.currentTimeMillis() / 1000));
                response.writeBody("<h1>set cookieTime</h1>");
            } else {
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>default</h1>");
            }
            // 3. 把响应写回到客户端
            response.flush();
        } catch (IOException | NullPointerException e) {
            e.printStackTrace();
        } finally {
            try {
                // 这个操作会同时关闭 getInputStream 和 getOutputStream 对象
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws IOException {
        HttpServerV2 server = new HttpServerV2(9090);
        server.start();
    }
}

强化理解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 的流程.

import javax.print.attribute.standard.RequestingUserName;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

public class HttpRequest {
    private String method;
    private String url;
    private String version;
    private Map<String, String> headers = new HashMap<>();
    // url 中的参数和 body 中的参数都放到这个 parameters hash 表中.
    private Map<String, String> parameters = new HashMap<>();
    private Map<String, String> cookies = new HashMap<>();
    private String body;

    public static HttpRequest build(InputStream inputStream) throws IOException {
        HttpRequest request = new HttpRequest();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        // 1. 处理首行
        String firstLine = bufferedReader.readLine();
        String[] firstLineTokens = firstLine.split(" ");
        request.method = firstLineTokens[0];
        request.url = firstLineTokens[1];
        request.version = firstLineTokens[2];
        // 2. 解析 url
        int pos = request.url.indexOf("?");
        if (pos != -1) {
            String queryString = request.url.substring(pos + 1);
            parseKV(queryString, request.parameters);
        }
        // 3. 循环处理 header 部分
        String line = "";
        while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
            String[] headerTokens = line.split(": ");
            request.headers.put(headerTokens[0], headerTokens[1]);
        }
        // 4. 解析 cookie
        String cookie = request.headers.get("Cookie");
        if (cookie != null) {
            // 把 cookie 进行解析
            parseCookie(cookie, request.cookies);
        }
        // 5. 解析 body
        if ("POST".equalsIgnoreCase(request.method)
                || "PUT".equalsIgnoreCase(request.method)) {
            // 这两个方法需要处理 body, 其他方法暂时不考虑
            // 需要把 body 读取出来.
            // 需要先知道 body 的长度. Content-Length 就是干这个的.
            // 此处的长度单位是 "字节"
            int contentLength = Integer.parseInt(request.headers.get("Content-Length"));
            // 注意体会此处的含义~~
            // 例如 contentLength 为 100 , body 中有 100 个字节.
            // 下面创建的缓冲区长度是 100 个 char (相当于是 200 个字节)
            // 缓冲区不怕长. 就怕不够用. 这样创建的缓冲区才能保证长度管够~~
            char[] buffer = new char[contentLength];
            int len = bufferedReader.read(buffer);
            request.body = new String(buffer, 0, len);
            // body 中的格式形如: username=tanglaoshi&password=123
            parseKV(request.body, request.parameters);
        }
        return request;
    }

    private static void parseCookie(String cookie, Map<String, String> cookies) {
        // 1. 按照 分号空格 拆分成多个键值对
        String[] kvTokens = cookie.split("; ");
        // 2. 按照 = 拆分每个键和值
        for (String kv : kvTokens) {
            String[] result = kv.split("=");
            cookies.put(result[0], result[1]);
        }
    }

    private static void parseKV(String queryString, Map<String, String> parameters) {
        // 1. 按照 & 拆分成多个键值对
        String[] kvTokens = queryString.split("&");
        // 2. 按照 = 拆分每个键和值
        for (String kv : kvTokens) {
            String[] result = kv.split("=");
            parameters.put(result[0], result[1]);
        }
    }

    public String getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }

    public String getVersion() {
        return version;
    }

    public String getBody() {
        return body;
    }

    public String getParameter(String key) {
        return parameters.get(key);
    }

    public String getHeader(String key) {
        return headers.get(key);
    }

    public String getCookie(String key) {
        return cookies.get(key);
    }
}

2,创建 HttpResponse 类

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;

public class HttpResponse {
    private String version = "HTTP/1.1";
    private int status;
    private String message;
    private Map<String, String> headers = new HashMap<>();
    private StringBuilder body = new StringBuilder();
    private OutputStream outputStream = null;

    public static HttpResponse build(OutputStream outputStream) {
        HttpResponse response = new HttpResponse();
        response.outputStream = outputStream;
        return response;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void setHeader(String key, String value) {
        headers.put(key, value);
    }

    public void writeBody(String content) {
        body.append(content);
    }

    public void flush() throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write(version + " " + status + " " + message + "\n");
        headers.put("Content-Length", body.toString().getBytes().length + "");
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            bufferedWriter.write(entry.getKey() + ": " + entry.getValue() + "\n");
        }
        bufferedWriter.write("\n");
        bufferedWriter.write(body.toString());
        bufferedWriter.flush();
    }
}

3,创建 HttpServer 类

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServerV3 {
    static class User {
        // 保存用户的相关信息
        public String userName;
        public int age;
        public String school;
    }

    private ServerSocket serverSocket = null;
    // session 会话. 指的就是同一个用户的一组访问服务器的操作, 归类到一起, 就是一个会话.
    // 记者来采访你, 记者问的问题就是一个请求, 你回答的内容, 就是一个响应. 一次采访过程中
    // 涉及到很多问题和回答(请求和响应), 这一组问题和回答, 就可以称为是一个 "会话" (整个采访的过程)
    // sessions 中就包含很多会话. (每个键值对就是一个会话)
    private HashMap<String, User> sessions = new HashMap<>();

    public HttpServerV3(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true) {
            Socket clientSocket = serverSocket.accept();
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    process(clientSocket);
                }
            });
        }
    }

    public void process(Socket clientSocket) {
        // 处理核心逻辑
        try {
            // 1. 读取请求并解析
            HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
            HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
            // 2. 根据请求计算响应
            // 此处按照不同的 HTTP 方法, 拆分成多个不同的逻辑
            if ("GET".equalsIgnoreCase(request.getMethod())) {
                doGet(request, response);
            } else if ("POST".equalsIgnoreCase(request.getMethod())) {
                doPost(request, response);
            } else {
                // 其他方法, 返回一个 405 这样的状态码
                response.setStatus(405);
                response.setMessage("Method Not Allowed");
            }
            // 3. 把响应写回到客户端
            response.flush();
        } catch (IOException | NullPointerException e) {
            e.printStackTrace();
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void doGet(HttpRequest request, HttpResponse response) throws IOException {
        // 1. 能够支持返回一个 html 文件.
        if (request.getUrl().startsWith("/index.html")) {
            String sessionId = request.getCookie("sessionId");
            User user = sessions.get(sessionId);
            if (sessionId == null || user == null) {
                // 说明当前用户尚未登陆, 就返回一个登陆页面即可.

                // 这种情况下, 就让代码读取一个 index.html 这样的文件.
                // 要想读文件, 需要先知道文件路径. 而现在只知道一个 文件名 index.html
                // 此时这个 html 文件所属的路径, 可以自己来约定(约定某个 d:/...) 专门放 html .
                // 把文件内容写入到响应的 body 中
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                InputStream inputStream = HttpServerV3.class.getClassLoader().getResourceAsStream("index.html");
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                // 按行读取内容, 把数据写入到 response 中
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    response.writeBody(line + "\n");
                }
                bufferedReader.close();
            } else {
                // 用户已经登陆, 无需再登陆了.
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                response.writeBody("<html>");
                response.writeBody("<div>" + "您已经登陆了! 无需再次登陆! 用户名: " + user.userName + "</div>");
                response.writeBody(+ user.age + "</div>");
                response.writeBody("<div>" + user.school + "</div>");
                response.writeBody("</html>");
            }
        }
    }

    private void doPost(HttpRequest request, HttpResponse response) {
        // 2. 实现 /login 的处理
        if (request.getUrl().startsWith("/login")) {
            // 读取用户提交的用户名和密码
            String userName = request.getParameter("username");
            String password = request.getParameter("password");
//            System.out.println("userName: " + userName);
//            System.out.println("password: " + password);
            // 登陆逻辑就需要验证用户名密码是否正确.
            // 此处为了简单, 咱们把用户名和密码在代码中写死了.
            // 更科学的处理方式, 应该是从数据库中读取用户名对应的密码, 校验密码是否一致.
            if ("zhangsan".equals(userName) && "123".equals(password)) {
                // 登陆成功
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                // 原来登陆成功, 是给浏览器写了一个 cookie, cookie 中保存的是用户的用户名.
                // response.setHeader("Set-Cookie", "userName=" + userName);

                // 现有的对于登陆成功的处理. 给这次登陆的用户分配了一个 session
                // (在 hash 中新增了一个键值对), key 是随机生成的. value 就是用户的身份信息
                // 身份信息保存在服务器中, 此时也就不再有泄露的问题了
                // 给浏览器返回的 Cookie 中只需要包含 sessionId 即可
                String sessionId = UUID.randomUUID().toString();
                User user = new User();
                user.userName = "zhangsan";
                user.age = 20;
                user.school = "北京大学";
                sessions.put(sessionId, user);
                response.setHeader("Set-Cookie", "sessionId=" + sessionId);

                response.writeBody("<html>");
                response.writeBody("<div>欢迎您! " + userName + "</div>");
                response.writeBody("</html>");
            } else {
                // 登陆失败
                response.setStatus(403);
                response.setMessage("Forbidden");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                response.writeBody("<html>");
                response.writeBody("<div>登陆失败</div>");
                response.writeBody("</html>");
            }
        }
    }

    public static void main(String[] args) throws IOException {
        HttpServerV3 serverV3 = new HttpServerV3(9090);
        serverV3.start();
    }
}

4,insex.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>登录页面</title>
</head>
<body>
    <form method="post" action="/login">
        <div style="margin-bottom: 5px">
            <input type="text" name="username" placeholder="请输入名字">
        </div>
        <div style="margin-bottom: 5px">
            <input type="password" name="password" placeholder="请输入密码">
        </div>
        <div>
            <input type="submit" value="登录">
        </div>

    </form>
</body>
</html>

请求中没有Cookie

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

到此这篇关于Java模拟实现HTTP服务器项目实战的文章就介绍到这了,更多相关Java HTTP服务器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java/Android 实现简单的HTTP服务器

    目前在对Android的代码进行功能测试的时候,需要服务器返回一个数据来测试整个流程是否正确.不希望引入第三方的JAR包,因此需要一个特别简单的HTTP服务器. 网上查询了一下,找到可用的代码如下: import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundEx

  • Java使用NioSocket手动实现HTTP服务器

    NioSocket简单复习 重要概念 NioSocket里面的三个重要概念:Buffer.Channel.Selector Buffer为要传输的数据 Channel为传输数据的通道 Selector为通道的分配调度者 使用步骤 使用NioSocket实现通信大概如以下步骤: ServerSocketChannel可以通过configureBlocking方法来设置是否采用阻塞模式,设置为false后就可以调用register注册Selector,阻塞模式下不可以用Selector. 注册后,S

  • java编写Http服务器下载工具

    这个工具比较简单,用于配合另外一个工具进行文件传送,废话少说,上代码 import java.net.URL; import java.net.URLConnection; import java.io.File; import java.io.InputStream; import java.io.FileOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import org.apac

  • Java 如何实现一个http服务器

    在Java中可以使用HttpServer类来实现Http服务器,该类位于com.sun.net包下(rt.jar).实现代码如下: 主程序类 package bg.httpserver; import com.sun.net.httpserver.HttpServer; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.Executors; public class

  • java Socket实现简单模拟HTTP服务器

    最近复习计算机网络,复习完应用层之后对于理论知识还是没有一个深刻的概念,索性就动手用Java Socket API 模拟做一个HTTP服务器,巩固一下应用层的知识. HTTP基于TCP协议,协议采用了请求/响应模型.客户端向服务器发送一个请求,请求头包含请求的方法.URL.协议版本.以及包含请求修饰符.客户信息和内容的类似于MIME的消息结构.服务器以一个状态行作为响应,响应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息.实体元信息以及可能的实体内容--百度百科. 话不多说,还是直

  • Java模拟实现HTTP服务器项目实战

    目录 一,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] + [版本

  • 原生js模拟淘宝购物车项目实战

    本文实例讲述了原生js模拟淘宝购物车实现代码.分享给大家供大家参考.具体如下: 通过JavaScript实现类似与淘宝的购物车效果,包括商品的单选.全选.删除.修改数量.价格计算.数目计算.预览等功能的实现.实现的效果图: 相应的代码: shoppingCart.html <!DOCTYPE html> <html> <head> <meta charset = "UTF-8"> <title>JavaScript实现购物车项

  • java模拟客户端向服务器上传文件

    本文实例为大家分享了java客户端向服务器上传文件的具体代码,供大家参考,具体内容如下 先来了解一下客户端与服务器Tcp通信的基本步骤: 服务器端先启动,然后启动客户端向服务器端发送数据. 服务器端收到客户端发送的数据,服务器端会响应应客户端,向客户端发送响应结果. 客户端读取服务器发送的数据 文件上传步骤: 客户端使用本地字节输入流,指定上传数据的数据源. 客户端使用网络字节输出流,把读取的本地文件上传到服务器. 服务器使用网络字节输入流,读取客户端上传的文件. 服务器使用本地字节输出流,把读

  • 详解python项目实战:模拟登陆CSDN

    前言 今天为大家介绍一个利用Python模拟登陆CSDN的案例,虽然看起来很鸡肋,有时候确会有大用处,在这里就当做是一个案例练习吧,提高自己的代码水平,也了解Python如何做到模拟登陆的, 下面来看代码 导入库 获取头部信息 解析网页 返回登录过后的session 检测是否登陆正常 运行结果 以上所述是小编给大家介绍的python项目实战:模拟登陆CSDN详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的.在此也非常感谢大家对我们网站的支持!

  • Java项目实战之在线考试系统的实现(系统介绍)

    1.本系统和现在有的考试系统有以下几种优势: a.和现在有的系统比较起来,本系统有科目.章节.老师.学生.班级等信息的管理,还有批阅试卷查看已批阅试卷等.传统的考试系统划分并不细,业务功能简单. b.和学校的考试系统还有外面的考试系统比较起来,本系统是B/S结构,学校的考试系统一般为C/S结构,性能方面不如B/S结构,并且C/S接口需要安装客户端,客户端压力很大,我的系统只需要电脑具有浏览器,在同一局域网就可以进行考试. c.从架构方面来讲,我们的系统为分布式架构,传统的考试系统没有我们的架构合

  • Java验证时间格式是否正确方法类项目实战

    在很多场景中我们需要验证时间日期的是否属于正确的格式,验证时间是否符合常规的. 1.验证 yyyy-MM-dd HH:mm:dd 格式的日期 String date = "2020-01-25 12:36:45"; System.out.println("date "+isLegalDate(date.length(),date,"yyyy-MM-dd HH:mm:ss")); 2.验证 yyyy-MM-dd 格式的日期 String yearM

  • 从javascript语言本身谈项目实战

    从javascript语言本身谈项目实战                                              dulao5  2005-1-15 随着ajax的升温,javascript越来越得到人们的重视.重要的是,ajax在一定程度上带来了web软件架构上的变化,人们把越来越多的功能分配到客户端实现,javascript子项目规模越来越大.如何更高效的使用javascript,如何更科学的组织javascript,如何更顺利的保证项目进展?我想就我的经验谈一点浅见. 一

  • vue项目实战总结篇

    这篇文章把小编前段时间做的vue项目,做个完整的总结,具体内容请参考本文. 这次算是详细总结,会从项目的搭建,一直到最后的服务器上部署. 废话不多说了.干货直接上. 一. 必须node环境, 这次就不写node环境的安装了.过两天我会写个node环境的安装随笔. 二. node环境配好后.开整vue. 1. 安装vue脚手架. npm install -g vue-cli 2. 用脚手架搭项目(只是一行命令) vue init webpack-simple (项目名字) 或 vue init w

  • Centos8.3、docker部署springboot项目实战案例分析

    引言 目前k8s很是火热,我也特意买了本书去学习了一下,但是k8s动辄都是成百上千的服务器运维,对只有几台服务器的应用来说使用k8s就有点像大炮打蚊子.只有几台服务器的应用运维使用传统的tomcat部署很繁琐,效率不高,动辄十几分钟部署一台服务,使用jenkins部署又太过复杂,斟酌许久我还是选择了使用docker+dockerFile的方式部署.这种方式部署简单高效. docker安装 curl -fsSL https://get.docker.com | bash -s docker --m

  • SpringBoot 整合 Spring-Session 实现分布式会话项目实战

    目录 一.配置及开发 二.测试 三.Spring-Session 的缺点 文章参考: Spring 提供了处理分布式会话的解决方案:Spring-Session.Spring-Session 提供了对Redis.MongoDB.MySQL 等常用存储的支持,Spring-Session 提供与 HttpSession 的透明整合,这意味着开发人员可以使用 Spring-Session 支持的实现方式,切换 HttpSession 至 Spring-Session.本文采用 Redis 作为第三方

随机推荐