基于Fetch的HTTP透明代理

本文从属于笔者的Web前端中DOM系列文章.

笔者在浏览器跨域方法与基于Fetch的Web请求最佳实践一文中介绍了浏览器跨域的基本知识与Fetch的基本使用,在这里要提醒两个前文未提到的点,一个是根据附带凭证信息的请求这里描述的,当你为了配置在CORS请求中附带Cookie等信息时,来自于服务器的响应中的Access-Control-Allow-Origin不可以再被设置为 * ,必须设置为某个具体的域名,则响应会失败。另一个就是因为Fetch中不自带Cancelable与超时放弃功能,往往需要在代理层完成。笔者在自己的工作中还遇到另一个请求,就是需要在客户端抓取其他没有设置CORS响应或者JSONP响应的站点,而必须要进行中间代理层抓取。笔者为了尽可能小地影响逻辑层代码,因此在自己的封装中封装了如下方法:

/  * @function 通过透明路由,利用get方法与封装好的QueryParams形式发起请求 * @param BASE_URL 请求根URL地址,注意,需要添加http://以及末尾的/,譬如`http://api.com/` * @param path 请求路径,譬如"path1/path2" * @param queryParams 请求的查询参数 * @param contentType 请求返回的数据格式 * @param proxyUrl 请求的路由地址 */getWithQueryParamsByProxy({BASE_URL=Model.BASE_URL, path="/", queryParams={}, contentType="json", proxyUrl="http://api.proxy.com"}) {    //初始化查询字符串,将BASE_URL以及path进行编码    let queryString = `BASE_URL=${encodeURIComponent(BASE_URL)}&path=${encodeURIComponent(path)}&`;    //根据queryParams构造查询字符串    for (let key in queryParams) {        //拼接查询字符串        queryString += `${key}=${encodeURIComponent(queryParams[key])}&`;    }    //将查询字符串进行编码    let encodedQueryString = (queryString);    //封装最终待请求的字符串    const packagedRequestURL = `${proxyUrl}?${encodedQueryString}action=GET`;    //以CORS方式发起请求    return this._fetchWithCORS(packagedRequestURL, contentType);}

另外自带缓存的透明代理层的配置为,代码存放于Github仓库:

/  * Created by apple on 16/7/26. */var express = require('express');var cors = require('cors');import Model from "../model/model";import ServerCache from "./server_cache";//创建服务端缓存实例const serverCache = new ServerCache();/  * @region 全局配置 * @type {string} */const hashKey = "ggzy"; //缓存的Hash值const timeOut = 5; //设置超时时间,5秒/  * @endregion 全局配置 *///添加跨域支持var app = express(cors());//默认的GET类型的透明路由app.get('/get_proxy', cors(), (req, res)=> {    //所有查询参数是以GET方式传入    //获取原地址    let BASE_URL = decodeURIComponent(req.query.BASE_URL);    //获取原路径    let path = decodeURIComponent(req.query.path);    //反序列化请求参数集合    let params = {};    //构造生成的全部的字符串    let url = "";    //遍历所有传入的参数集合    for (let key in req.query) {        if (key == "BASE_URL" || key == "path") {            //对于传入的根URL与路径直接忽略,            //封装其他参数            continue;        } else {            params[key] = decodeURIComponent(req.query[key]);        }        url += `${key}${req.query[key]}`;    }    //判断缓存中是否存在值    serverCache.get(hashKey, url).then((data)=> {        //如果存在数据        res.set('Access-Control-Allow-Origin', '*');        res.send(data);        res.end();    }).catch((error)=> {        //如果不存在数据,执行数据抓取        //发起GET形式的请求        const model = new Model();        //判断是否已经返回        let isSent = false;        //使用模型类发起请求,并且不进行解码直接返回        model.getWithQueryParams({            BASE_URL,            path,            params,            contentType: "text" //不进行解码,直接返回        }).then((data)=> {            if (isSent) {                //如果已经设置了超时返回,则直接返回                return;            }            //返回抓取到的数据            res.set('Access-Control-Allow-Origin', '*');            res.send(data);            res.end();            isSent = true;        }, (error)=> {            if (isSent) {                //如果已经设置了超时返回,则直接返回                return;            }            //如果直接抓取失败,则返回无效信息            res.send(JSON.stringify({                "message": "Invalid Request"            }));            isSent = true;            throw error;        });        //设置秒超时返回N        setTimeout(            ()=> {                if (isSent) {                    //如果已经设置了超时返回,则直接返回                    return;                }                //设置返回超时                res.status(504);                //终止本次返回                res.end();                isSent = true;            },            1000 * timeOut        );    });});//设置POST类型的默认路由//默认的返回值app.get('/', function (req, res) {    res.send('Hello World!');    res.end();});//启动服务器var server = app.listen(399, '0.0.0.0', function () {    var host = server.address().address;    var port = server.address().port;    console.log('Example app listening at http://%s:%s', host, port);});

笔者在这里是使用Redis作为缓存:

/  * Created by apple on 16/8/4. */var redis = require("redis");export default class ServerCache {    /      * @function 默认构造函数     */    constructor() {        //构造出Redis客户端        this.client = redis.createClient();        //监听Redis客户端创建错误        this.client.on("error", (err) => {            this.client = null;            // console.log("Redis Client Error " + err);        });    }    /      * @function 从缓存中获取数据     * @param hashKey     * @param url     * @returns {Promise}     */    get(hashKey = "hashKey", url = "url") {        return new Promise((resolve, reject)=> {            if (!!this.client) {                //从Redis中获取数据                this.client.hget(hashKey, url, function (err, replies) {                    //如果存在数据                    if (!!replies) {                        resolve(replies);                    } else {                        reject(err);                    }                });            } else {                reject(new Error("Invalid Client"));            }        });    }    /      * @function 默认将数据放置到缓存中     * @param hashKey 存入的键     * @param url 存入的域URL     * @param data 存入的数据     * @param expire 第一次存入时候的过期时间     * @result 如果设置失败,则返回null     */    put(hashKey = "hashKey", url = "url", data = "data", expire = 60 * 60 * 6 * 1000) {        //判断客户端是否有效        if (!this.client) {            //如果客户端无效,直接返回null            return null;        }        //第一次设置的时候判断ggzy是否存在,如果不存在则设置初始值        this.client.hlen(hashKey, function (err, replies) {            //获取键值长度,第一次获取时候长度为0            if (replies == 0) {                //12小时之后删除数据                client.expire(hashKey, expire);            }        });        //设置数据        client.hset(hashKey, url, data);    }}

注意,笔者在这里使用的是isomorphic-fetch,因此在服务端与客户端的底层请求上可以复用同一份代码,测试代码如下,直接使用babel-node model.test.js即可:

/  * Created by apple on 16/7/21. */import Model from "./model";const model = new Model();//正常的发起请求model    .getWithQueryParams({        BASE_URL: "http://ggzy.njzwfw.gov.cn/njggzy/jsgc/",        path: "001001/001001001/001001001001/",        queryParams: {            Paging: 100        },        contentType: "text"    })    .then(        (data)=> {            console.log(data);        }    )    .catch((error)=> {        console.log(error);    });//使用透明路由发起请求model    .getWithQueryParamsByProxy({        BASE_URL: "http://ggzy.njzwfw.gov.cn/njggzy/jsgc/",        path: "001001/001001001/001001001001/",        queryParams: {            Paging: 100        },        contentType: "text",        proxyUrl: "http://153.3.251.190:11399/"    })    .then(        (data)=> {            console.log(data);        }    )    .catch((error)=> {        console.log(error);    });

关键字:dom, fetch


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部