动态服务器(AJAX应用)

158 阅读4分钟

动态网络服务器(dynamic web server)

由一个静态的网络服务器加上额外的软件组成,最普遍的是一个应用服务器和一个数据库
我们称它为 “动态” 是因为这个应用服务器会在通过 HTTP 服务器把托管文件传送到你的浏览器
之前会对这些托管文件进行更新。

构建一个模拟的数据库,结构是一个数组 /db/user.json

[
  {"id":"1", "name":"frank", "password":"***", "age":"18"},
  {"id":"2", "name":"jack", "password":"***", "age":"20"},
]

获取数据库的内容

const fs = require("fs"); 

读数据库

const usersString = fs.readFileSync("./db/users.json").toString();
const usersArray = JSON.parse(usersString);

写数据库

const users = { id: 3, name: 'tom', password: 'yyy' }
usersArray.push(users)
const string = JSON.stringify(usersArray)
fs.writeFileSync('./db/users.json', string)

实现用户注册功能

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

思路
前端写一个form,让用户填写name和password
前端监听submit事件
前端发送post请求,数据位于请求体
后端接收post数据
后端获取请求体中的name和password
后端储存数据

代码
<body>
    <form id="registerForm">
        <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 = $('#registerForm')
        $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: '/register',
                contentType: 'text/json; charset=UTF-8',
                data: JSON.stringify({ name, password })
            }).then(() => {
                alert('注册成功')
                location.href = '/sign_in.html'
            }, () => { }
            )
        })
    </script>
</body>

实现用户登录功能

首页home.html,已登录用户可以看到自己的用户名
登录页sign_in.html,供提交用户名和密码
输入的用户名和密码如果是匹配的,就自动跳转到登录页

sign_in.html思路
前端写一个form,让用户填写name和password
前端监听submit事件
前端发送post数据,数据位于请求体
后端接收post请求
后端获取请求体中的name和password
后端读取数据,看是否有匹配的name和password
如果匹配,后端标记用户已登录(如何标记)

代码
<body>
    <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',
                contentType: 'text/json; charset=UTF-8',
                data: JSON.stringify({ name, password })
                }).then(() => {
                alert('登陆成功')
                location.href = '/home.html'
            }, () => { }
            )
        })
    </script>
</body>

标记用户已登录

cookie定义
cookie是服务器下发给浏览器的一段字符串
浏览器必须保存这个cookie(除非用户删除)
之后发起相同二级域名请求(任何请求)时,浏览器必须附上cookie

Set-Cookie 具体语法

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie

显示用户名

home.html渲染前获得user信息
如果有user,则将{{user.name}} 替换成 user.name
如果无user,则显示登陆按钮

防止篡改user_id

把信息隐藏在服务器
把用户信息放在服务器x里,再给信息一个随机id
把随机id发给服务器
后端下次读到id时,通过x[id]获取用户信息
因为id很长,完全随机,所以无法篡改id
x是文件,不能使用内存,因为断电内存就会清空
这个x又被叫做session(会话)

cookie session总结

服务器可以给浏览器下发cookie
通过Response Header

浏览器上的cookie可以被篡改
用开发者工具就能改
后端下发的cookie用js也能改

服务器下发不可修改的cookie
cookie可包含加密后的信息(需要解密,麻烦,容易被盗)
cookie也可只包含一个id(随机数)
用session[id]可以在后端拿到对应的信息
这个id不会被篡改,但是会被复制

服务器代码

const session = JSON.parse(fs.readFileSync('./session.json').toString())
console.log('有个帅哥发请求过来啦!路径(带查询参数)为:' + pathWithQuery)
if (path === "/sign_in" && method === "POST") {
    response.setHeader('Content-Type', 'text/html; charset=utf-8');
    const userArray = 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);
        const user = userArray.find((user) => user.name === obj.name &&
        user.password === obj.password)
        if (user === undefined) {
            response.statusCode = 400
            response.setHeader('Content-Type', 'text/json; charset=utf-8');
            response.end(`{"errorCode:4001"}`)
            } else {
            response.statusCode = 200
            const random = Math.random()
            session[random] = { user_id: user.id }
            fs.writeFileSync('session.json', JSON.stringify(session))
            response.setHeader('Set-Cookie', `session_id=${random}; 'HttpOnly'`)
            }
            response.end()
        });
    } else if (path === "/home.html") {
        const cookie = request.headers["cookie"];
        let sessionId
        try {
            sessionId = cookie.split(';').filter(s =>
            s.indexOf('session_id=') >= 0)[0].split('=')[1]
        } catch (error) { }
        if (sessionId && session[sessionId]) {
            const userId = session[sessionId].user_id
            const userArray = JSON.parse(fs.readFileSync("./db/users.json"));
            const user = userArray.find(user => user.id === userId);
            const homeHtml = fs.readFileSync("./public/home.html").toString();
            let string = ''
            if (user) {
                string = homeHtml.replace("{{loginStatus}}", '已登录')
                .replace('{{user.name}}', user.name)
            }
            response.write(string);
        } else {
            const homeHtml = fs.readFileSync("./public/home.html").toString();
            const string = homeHtml.replace('{{loginStatus}}', '未登录')
            .replace('{{user.name}}', '')
            response.write(string);
        }
        response.end()
    } else if (path === "/register" && method === "POST") {
        response.setHeader('Content-Type', 'text/html; charset=utf-8');
        const userArray = 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);
            const lastUser = userArray[userArray.length - 1];
            const newUser = {
                id: lastUser ? lastUser.id + 1 : 1,
                name: obj.name,
                password: obj.password
            };
            userArray.push(newUser);
            fs.writeFileSync("./db/users.json", JSON.stringify(userArray));
            response.end()
        });
    } else {
        response.statusCode = 200
        const filePath = path === '/' ? '/index.html' : path
        const index = filePath.lastIndexOf('.')
        const suffix = filePath.substring(index)
        const fileTypes = {
            '.html': 'text/html',
            '.css': 'text/css',
            '.js': 'text/javascript',
            '.png': 'image/png',
            '.img': 'image/jpeg'
        }
        response.setHeader('Content-type', `${fileTypes[suffix] ||
        'text/html'};charset=utf-8`)
        let content
        try {
            content = fs.readFileSync(`./public${filePath}`)
        } catch (error) {
            content = '文件不存在'
            response.statusCode = 404
        }
        response.write(content)
        response.end()
    }
})