《静态服务器和动态服务器》

211 阅读6分钟

一. 区别

没有请求数据库,就是静态服务器。

请求了数据库,就是动态服务器。

二. 动态服务器

用json文件当作数据库。

//users.json
[
    {"id":"1","name":"anqi","password":"xxx"},
    {"id":"2","name":"susu","password":"zzz"}
]

1. 读数据

//test.js
const fs = require("fs");
const usersString = fs.readFileSync("./db/users.json").toString();
const usersArray = JSON.parse(usersString);  //反序列化

2. 存数据

const user3 = { id: 3, name: "jane", password: "qqq" };
usersArray.push(user3);
const string = JSON.stringify(usersArray);  //序列化
fs.writeFileSync("./db/users.json", string);

运行 node test.js

3. 实现用户注册功能

用户提交用户名和密码,users.json就新增了一行数据。

思路:

前端:

  • 在注册页面,写一个form表单,用户填写用户名和密码
  • 监听form的submit事件,当用户点击注册按钮时,触发事件
  • 向服务器发送POST请求,数据(得到的用户填写的用户名和密码)位于请求体

后端:

  • 接收post请求
  • 获取请求体中的数据
  • 得到数据完成后,把数据存进数据库(users.json)

前端代码:

//register.html
<form id="registerForm">
    <div>  //经常用label标签包住input
        <label>用户名 <input type="text" name="name" /></label>
    </div>
    <div>
        <label>密码 <input type="password" name="password" /></label>
    </div>
    <div>
        <button type="submit">注册</button>
    </div>
</form>

//用jquery库
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>

<script>
    const $form = $("#registerForm");
    $form.on("submit", (e) => {
        e.preventDefault(); //阻止表单的默认事件,使点击按钮不刷新
        //找到form里的name=name的input标签,得到填入的值
        const name = $form.find("input[name=name]").val();  
        const password = $form.find("input[name=password]").val();
        //用jquery.ajax发送一个POST请求
        $.ajax({
          method: "POST",
          url: "/register",
          contentType: "text/json;charset=UTF-8",
          data: JSON.stringify({ name, password }),   //把要上传的数据放到data里
        }).then(() => {
          alert("注册成功");
          location.href = "/sign_in.html";
        });
    });
</script>

在后端服务器代码里,新开一个路由

//server.js
if (path === "/register" && method === "POST") {
    response.setHeader("Content-Type", "text/html;charset=utf-8");
    //先把数据库读出来
    const usersArray = JSON.parse(fs.readFileSync("./db/users.json"));
    const lastUser = usersArray[usersArray.length - 1];
    const array = [];
    request.on("data", (chunk) => {
      array.push(chunk);
    });
    request.on("end", () => {
      //把utf-8编码的array变成字符串形式
      const string = Buffer.concat(array).toString();
      const obj = JSON.parse(string);
      const newUser = {
        id: lastUser ? lastUser.id + 1 : 1,
        name: obj.name,
        password: obj.password,
      };
      usersArray.push(newUser);
      fs.writeFileSync("./db/users.json", JSON.stringify(usersArray));
      response.end();
    });
}

获取到GET请求携带的内容,可以用query,查询参数。POST请求可以监听request的"data"事件,由于POST请求上传的数据可能很大,需要一点点接收,所以,监听到数据传过来一点,就接收一点。什么时候数据接收完毕呢?监听request的"end"事件,数据接收结束了,就转换成正确的格式,得到传过来的数据,把它放进数据库。

4. 实现用户登录功能

1. 思路

  • 登录页sign_in.html,用户填写用户名和密码,并且提交
  • 首页home.html,用户点击登录,跳转到首页,已登录的用户可以看到自己的用户名
  • 输入的用户名和密码如果匹配,即数据库里能找到,就跳转。

前端:

  • 在登录页面写一个form表单,供用户提交用户名和密码
  • 监听form的submit事件,当监听到用户点击按钮时,触发相关事件
  • 发送POST请求,数据位于请求体

后端:

  • 接收POST请求
  • 获取到请求体中的数据,name和password
  • 去数据库中查找,是否和上传的name,password相匹配
  • 如果匹配,标记用户已登录,在home.html里显示已登录

前端代码:

//sign_in.html
<form id="signInForm">
    <div>
        <label>用户名 <input type="text" name="name" /></label>
    </div>
    <div>
        <label>密码 <input type="password" name="password" /></label>
    </div>
    <div>
        <button type="submit">登录</button>
    </div>
</form>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    const $form = $("#signInForm");
    $form.on("submit", (e) => {
        e.preventDefault();
        const name = $form.find("input[name=name]").val();
        const password = $form.find("input[name=password]").val();
        $.ajax({
          method: "POST",
          url: "/sign_in",  //请求/sign_in路由
          contentType: "text/json;charset=UTF-8",
          data: JSON.stringify({ name, password }),
        }).then(() => {
          alert("登录成功");
          location.href = "/home.html";  //点击确定按钮后,跳转到home.html
        });
    });
</script>

后端服务器代码:

//server.js
if (path === "/sign_in" && method === "POST") {  //必须是POST请求
    response.setHeader("Content-Type", "text/html;charset=utf-8");
    const usersArray = JSON.parse(fs.readFileSync("./db/users.json"));
    const array = [];
    request.on("data", (chunk) => {
      array.push(chunk);
    });
    request.on("end", () => {
      const string = Buffer.concat(array).toString();
      const obj = JSON.parse(string); //obj.name,obj.password得到了
      const user = usersArray.find(function (user) {
        return user.name === obj.name && user.password === obj.password;
      });
      //如果有匹配的,请求成功
      if (user) {
        response.statusCode = 200;
        //设置cookie
        response.setHeader("Set-Cookie", "logined=1");
        response.end();
      } else {
        response.statusCode = 400; //错了
        response.setHeader("Content-Type", "text/json;charset=utf-8");
        response.end(`{"errorCode":531}`); //错误码是自己定的,响应json字符串
      }
    });
}

在首页home.html,先设置一个占位符。怎么区分是从登陆页面跳转的(应该显示已登录),还是直接在地址栏输入的home.html呢(应该显示未登录)?

2. 使用Cookie标记用户

cookie就像一张门票,只要在后端服务器发现上传的用户名和密码相匹配,就给浏览器发一张门票,也就是设置一个cookie,浏览器会一直保存这个cookie(除非用户删除)。

用户在sign_in.html页面点击登录时,就会向 /sign_in路由发送POST请求,如果成功,通过then方法先弹出登录成功的提示框,点击确定后,跳转到首页。

服务器接收到向/sign_in的请求,先拿到POST上传的数据,去数据库里查找有没有这个用户,如果有,就代表请求成功,并且给浏览器发一个cookie,标记此用户已登录。如果没有这个用户,就失败,返回一段错误码。

同时,如果请求成功了,在客户端就依次弹出提示框,再去请求首页。服务端接收到浏览器发来的/home.html的请求,先看看浏览器带不带cookie,如果带了,服务器就知道已登录,就把首页里的占位符替换成‘已登录’字符串,响应回去;如果服务器发现浏览器发过来的请求没有cookie,就说明没登录,就显示‘未登录’字符串,作为响应。

response.setHeader("Set-Cookie", "logined=1")

//server.js
else if (path === "/home.html") {
    const cookie = request.headers["cookie"];
    if (cookie === "logined=1") {
      const homeHtml = fs.readFileSync("./public/home.html").toString();
      const string = homeHtml.replace("{{loginStatus}}", "已登录");
      response.write(string);
    } else {
      const homeHtml = fs.readFileSync("./public/home.html").toString();
      const string = homeHtml.replace("{{loginStatus}}", "未登录");
      response.write(string);
    }
    response.end();
}

也就是说,服务器在浏览器请求/sign_in时,经查找如果有这个用户,就给浏览器发一个cookie。也是服务器在浏览器再次对home.html发起请求时,读取请求头里的cookie。都是在后端操作的。

前端最好不要操作cookie。这里Cookie的HttpOnly标记,可以使Cookie 不被客户端 JavaScript 脚本调用。表示Cookie只被发送给服务端,通过 Document.cookie API无法访问带有 HttpOnly 标记的Cookie。 response.setHeader("Set-Cookie", "logined=1; HttpOnly")

3. 使用cookie记录user.id

现在我们使用cookie标记了用户是否登录,如果已登录,我们还想显示登录的用户名。只需要在/sign_in路由里,如果这个用户存在,把cookie的值由'logined=1'改成标记这个用户的id。

//server.js
if (user) {
    response.statusCode = 200;
    response.setHeader("Set-Cookie", `user_id=${user.id}; HttpOnly`);
    response.end();
}

然后浏览器就带着cookie回去了。紧接着浏览器发起对首页的请求,在服务器代码里,如果请求了首页,先得到随着请求来的cookie,从中拿到user.id,去数据库里找对应的用户,得到user.name,还是用字符串的replace方法,把home.html里的内容换成用户名响应回去。

else if (path === "/home.html") {
    const homeHtml = fs.readFileSync("./public/home.html").toString();
    const cookie = request.headers["cookie"];
    let userId;
    try {
      userId = cookie
        .split(";")
        .filter((s) => s.indexOf("user_id=") >= 0)[0]
        .split("=")[1];
    } catch (error) {}
    if (userId) {
      const usersArray = JSON.parse(fs.readFileSync("./db/users.json"));
      const user = usersArray.find((user) => user.id.toString() === userId)
      let string;
      string = homeHtml
        .replace("{{loginStatus}}", "已登录")
        .replace("{{username}}", user.name);
      response.write(string);
      response.end();
    } else {
      let string = homeHtml
        .replace("{{loginStatus}}", "未登录")
        .replace("{{username}}", "");
      response.write(string);
      response.end();
    }
}

在home.html里做的事情就是,先设置两个占位符,根据是否有cookie,以及从cookie中得到的user.id,把占位符替换成真正要显示的内容。

登录成功,点击确定后,跳转到首页:

在无痕窗口直接输入首页的地址,就不会有cookie:

4. session会话保存用户信息

上边的代码有一个bug,我们把user.id写到cookie里,就导致用户可以篡改。怎么防止用户篡改呢?不把user.id这个信息展示出来,而是把它放到服务器里的session.json文件里,再给信息一个随机的id,我们把这个随机数放到cookie里给浏览器。这样,后端下次接收到home.html的请求时,读出来这个随机的id,然后去session.json文件里,通过session[id]读到真正的用户id。

//server.js   /sign_in
if (user) {
        response.statusCode = 200;
        const random = Math.random();
        const session = JSON.parse(fs.readFileSync("./session.json"));
        session[random] = { user_id: user.id };
        fs.writeFileSync("./session.json", JSON.stringify(session));
        response.setHeader("Set-Cookie", `session_id=${random}; HttpOnly`);
        response.end();
}

在服务器给浏览器发cookie的同时,就开启了session会话,把session数据存进session.json文件里,这个文件就保存了用户的信息。当用户点击登录并且登陆成功时,浏览器的响应里就会出现cookie:

可以看见,cookie的内容是session_id是一串随机数。同时,在session.json文件里,就会保存上对应的信息:
然后在home.html的响应里,由sessionId得到真正的user.id。