# 路由

BS 架构中,路由的概念都是一样的,可理解为根据客户端请求的 URL 映射到不同的方法实现,更多的一般都是针对 URL 中的路径,或者是参数,又或者是锚点这些信息进行映射。

# 场景

  1. 注册一个账户 --> [post] --> http://localhost:88/register
  2. 注册成功的情况下跳转到登录界面进行登录 --> [post] --> http://localhost:88/login
  3. 登录成功进行获取学生信息 --> [get] --> http://localhost:88/students
  4. 同时可以获取订单信息 --> [get] --> http://localhost:88/orders
  5. 如何访问不存在的路由则抛出错误信息。
const http = require('http')
const url = require('url')
const qs = require('querystring');
const util = require('util');

http.createServer((request, response) => {
    let urlObj = url.parse(request.url, true);
    let pathname = urlObj.pathname;
    let method = request.method.toUpperCase();
    let params = urlObj.query;
    if(method == 'POST'){
        let postData = '';
        request.on('data', (_data) => {
            postData += '_data';
        })
        request.on('end', () => {
            postData = qs.parse(postData);
            let result = {};
            switch(pathname){
                case '/login':
                    //连接数据库,实现登陆逻辑
                    result = {status: true};
                    break;
                case '/register':
                    //连接数据库,实现注册逻辑
                    result = {status: true};
                    break;
                default :
                    result = {status: false, message: '没有对应的请求'};
                    break;                  
            }
            response.end(util.inspect(result))
        })
    } else {
        let result = {};
        switch(pathname){
            case '/students':
                //连接数据库,获取学生信息
                result = {status: true, data: [], params};
                break;
            case '/orders':
                //连接数据库,获取订单信息
                result = {status: true, data: [], params};
                break;
            default :
                result = {status: false, message: '没有对应的请求', params};
                break;
        }
        response.end(util.inspect(result))
    }
}).listen(88)

# Demo

const http = require('http')
const url = require('url')
const qs = require('querystring');
const util = require('util');

http.createServer((request, response) => {
    let urlObj = url.parse(request.url, true);
    let pathname = urlObj.pathname;
    let method = request.method.toUpperCase();
    let params = urlObj.query;
    if(method == 'POST'){
        let postData = '';
        request.on('data', (_data) => {
            postData += '_data';
        })
        request.on('end', () => {
            postData = qs.parse(postData);
            let result = {};
            switch(pathname){
                case '/login':
                    //连接数据库,实现登陆逻辑
                    result = {status: true};
                    break;
                case '/register':
                    //连接数据库,实现注册逻辑
                    result = {status: true};
                    break;
                default :
                    result = {status: false, message: '没有对应的请求'};
                    break;                  
            }
            response.end(util.inspect(result))
        })
    } else {
        let result = {};
        switch(pathname){
            case '/students':
                //连接数据库,获取学生信息
                result = {status: true, data: [], params};
                break;
            case '/orders':
                //连接数据库,获取订单信息
                result = {status: true, data: [], params};
                break;
            default :
                result = {status: false, message: '没有对应的请求', params};
                break;
        }
        response.end(util.inspect(result))
    }
}).listen(88)

# request

一个第三方的模块,可用于发起 http 或 https 请求,可理解成服务端的 ajax 请求。可用于代简单的服务器代理,用法和 ajax 类似。

在使用前需要先安装 npm install request --save

# GET 请求

request.get('https://cnodejs.org/api/v1/topics?page=1&limit=10', (error, response, body) => {
    console.log(body)
})
//or
request('https://cnodejs.org/api/v1/topics?page=1&limit=10', (error, response, body) => {
    console.log(body)
})

# 多参数设置

exports.get = function(url, options) {
    options = options || {};
    var httpOptions = {
        url: url,
        method: 'get',
        timeout: options.timeout || 10000,
        headers: options.headers || default_post_headers,
        proxy: options.proxy || '',
        agentOptions: agentOptions,
        params: options.params || {}
    }
    if(options.userAgent){
        httpOptions.headers = {
            'User-Agent': userAgents[options.userAgent],
        }
    }

    try{
        request.get(httpOptions, function(err, res, body) {
            if (err) {
                options.callback({status: false, error: err})
            } else {
                options.callback({status: res.statusCode == 200, error: res, data: body})
            }
        }).on('error', logger.error);
    } catch(err){
        console.log('http error');
    }
}

# POST 请求

request支持application/x-www-form-urlencoded和multipart/form-data实现表单上传。

# application/x-www-form-urlencoded (URL-Encoded Forms)

request.post('http://service.com/upload', {form:{key:'value'}})
// or
request.post('http://service.com/upload').form({key:'value'})
// or
request.post({url:'http://service.com/upload', form: {key:'value'}}, function(err,httpResponse,body){ /* ... */ })

# multipart/form-data (Multipart Form Uploads)

var formData = {
  // Pass a simple key-value pair
  my_field: 'my_value',
  // Pass data via Buffers
  my_buffer: new Buffer([1, 2, 3]),
  // Pass data via Streams
  my_file: fs.createReadStream(__dirname + '/unicycle.jpg'),
  // Pass multiple values /w an Array
  attachments: [
    fs.createReadStream(__dirname + '/attachment1.jpg'),
    fs.createReadStream(__dirname + '/attachment2.jpg')
  ],
  // Pass optional meta-data with an 'options' object with style: {value: DATA, options: OPTIONS}
  // Use case: for some types of streams, you'll need to provide "file"-related information manually.
  // See the `form-data` README for more information about options: https://github.com/form-data/form-data
  custom_file: {
    value:  fs.createReadStream('/dev/urandom'),
    options: {
      filename: 'topsecret.jpg',
      contentType: 'image/jpeg'
    }
  }
};
request.post({url:'http://service.com/upload', formData: formData}, function optionalCallback(err, httpResponse, body) {
  if (err) {
    return console.error('upload failed:', err);
  }
  console.log('Upload successful!  Server responded with:', body);
});

# 常用多参数设置

exports.form_post = function(url, postdata, options) {
    // console.log(`${moment().format()} HttpFormPost: ${url}`)
    return new Promise((resolve, reject) => {
        options = options || {};
        var httpOptions = {
            url: url,
            form: postdata,
            method: 'post',
            timeout: options.timeout || 3000,
            headers: options.headers || default_post_headers,
            proxy: options.proxy || '',
            agentOptions: agentOptions
        };
        request(httpOptions, function(err, res, body) {
            if (err) {
                reject(err);
            } else {
                if (res.statusCode == 200) {
                    resolve(body);
                } else {
                    reject(res.statusCode);
                }
            }
        }).on('error', logger.error);
    });
};

#

request('http://img.zcool.cn/community/018d4e554967920000019ae9df1533.jpg@900w_1l_2o_100sh.jpg').pipe(fs.createWriteStream('test.png'))
request('https://cnodejs.org/api/v1/topics?page=1&limit=10').pipe(fs.createWriteStream('cnodejs.json'))

# http 小爬虫

又被称为网页蜘蛛,网络机器人,主要是在服务端去请求外部的 url 拿到对方的资源,然后进行分析并抓取有效数据。

这里用 request 实现一个简单的图片抓取的小爬虫

const request = require('request');
const fs = require('fs');
const cheerio = require('cheerio');

request('http://www.lanrentuku.com/', (error, response, body) => {
    let $ = cheerio.load(body);
    $('img', '.in-ne').each((i, e) => {
        let src = $(e).attr('src');
        let name = src.substr(src.lastIndexOf('/') + 1);
        request(src).pipe(fs.createWriteStream(name))
    })
})

# async

Node.js 是一个异步机制的服务端语言,在大量异步的场景下需要按顺序执行,那正常做法就是回调嵌套回调,回调嵌套太多的问题被称之回调地狱。

Node.js 为解决这一问题推出了异步控制流 ———— Async

# Async/Await

Async/Await 就 ES7 的方案,结合 ES6 的 Promise 对象,使用前请确定 Node.js 的版本是 7.6 以上。

Async/await的主要益处是可以避免回调地狱(callback hell),且以最接近同步代码的方式编写异步代码。

# 基本规则

  • async 表示这是一个async函数,await只能用在这个函数里面。
  • await 表示在这里等待promise返回结果了,再继续执行。
  • await 后面跟着的应该是一个promise对象

# 对比使用

场景:3秒后返回一个值

# 原始时代

let sleep = (time, cb) => {
    setTimeout(() => {
        cb('ok');
    }, 3000);
}

let start = () => {
    sleep(3000, (result) => {
        console.log(result)
    })
}

start()

# Promise 时代

let sleep = (time) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
           resolve('ok') ;
        }, time);
    })
}

let start = () => {
    sleep(3000).then((result) => {
        console.log(result)
    })
}

start()

# Async/Await 时代

let sleep = (time) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
           resolve('ok') ;
        }, time);
    })
}

let start = async () => {
    let result = await sleep(3000);
    console.log(result)
}

start();

# 捕捉错误

let sleep = (time) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('error') ;
        }, time);
    })
}

let start = async () => {
    try{
        let result = await sleep(3000);
        console.log(result)
    } catch(err) {
        console.log('error')
    }
}

start();

# 在循环中使用

let sleep = (time) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('ok') ;
        }, time);
    })
}

let start = async () => {
    for (var i = 1; i <= 3; i++) {
        console.log(`当前是第${i}次等待..`);
        await sleep(1000);
    }
}

start();

# 爬虫中使用

const request = require('request');
const fs = require('fs');
const cheerio = require('cheerio');

let spider = (url) => {
    return new Promise((resolve, reject) => {
        request(url, (error, response, body) => {
            resolve(body);
        })
    })
}

let start = async () => {
    let dom = await spider('http://www.lanrentuku.com/');
    let $ = cheerio.load(dom);
    $('img', '.in-ne').each((i, e) => {
        let src = $(e).attr('src');
        let name = src.substr(src.lastIndexOf('/') + 1);
        request(src).pipe(fs.createWriteStream(name))
    })
}

start();
Last Updated: 2020/1/30 上午3:45:52