服务器作防盗链图片中转,nodejs 上手项目简明教程

前几天随手写的 chrome 插件遇到了防盗链问题,由于插件不能用 js iframe 的方法反防盗链,于是想用服务器做个中转。

记录一下上手项目的各个点,以后再用 nodejs 就不用到处查资料了。

之前没有一套特别熟悉的 web 开发框架,加上插件存储服务依赖的平台 LeanCloud 刚好支持部署 nodejs 网站,刚好拿这个小项目作为 nodejs 上手项目。


怎么"破解防盗链"呢?
想要破解,就得先知道目标——防盗链如何实现。
大多数站点的策略很简单: 判断request请求头的refer是否来源于本站。若不是,拒绝访问真实图片。

而我们知道: 请求头是来自于客户端,是可伪造的。

思路
那么,我们伪造一个正确的refer来访问不就行了?
整个业务逻辑大概像这样:  
1. 自己的服务器后台接受带目标图片url参数的请求
2. 伪造refer请求目标图片
3. 把请求到的数据作为response返回

这就起到了图片中转的作用。

1. 项目是什么样子

1.1 接口的样子?

  • 有一个开放接口
  • 接口有一个参数,api?url=http://abc.com/image.png,大概长这样子
  • 响应内容是反防盗链后的真实图片

1.2 应该怎么做?

  • 把服务器跑起来
  • 处理 GET 请求
  • 分析请求参数
  • 下载原图
  • response 原图

2. 学习路径(在对目标未知的前提下提出疑问)

  1. 如何开始,建立服务器
  2. 如何处理基本请求 GET POST
  3. 如何下载图片并转发
  4. 完成基本功能,上线
  5. 优化

2.1 如何开始,建立服务器

主要是 http.createServer().listen(port) 这组方法,建立服务器、监听端口一键搞定。

var http = require('http');
    
http.createServer(function (request, response) {
     // do things here
}).listen(8888);
    
console.log('Server running at: 8888');

2.2 如何处理基本请求 GET POST

createServer 回调方法的两个参数 req res 是 http requestresponse 的内容,打印一下他们的内容。

requestInComingMessage 类,打印它的 url 字段。

var http = require('http');
var url = require('url');
var util = require('util');
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end(util.inspect(url.parse(req.url, true)));
}).listen(3000);

请求
http://localhost:3000/api?url=http://abc.com/image.png

请求结果

Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?url=http://abc.com/image.png',
  query: { url: 'http://abc.com/image.png' },
  pathname: '/api',
  path: '/api?url=http://abc.com/image.png',
  href: '/api?url=http://abc.com/image.png' }

query 字段刚好是我们想要的内容,下载这个字段对应的图片。

2.3 如何下载图片并转发

request 模块支持管道方法,可以和 shell 的管道一样理解。

这可以省很多事,不需要在本地存储图片,不需要处理杂七杂八的事情,甚至不需要再去了解 nodejs 的流。一个方法全搞定。

关键方法: request(options).pipe(res)

    var options = {
      uri: imgUrl, // 这个 uri 为空时,会认为该字段不存在,报异常
      headers: {
         'Referer': referrer // 解决部分防盗链选项
      }
    };
    request(options).pipe(res);

2.4 完成基本功能,上线

项目地址

完整代码

    'use strict';
    var router = require('express').Router();
    var http = require('http');
    var url = require('url');
    var util = require('util');
    var fs = require('fs');
    var callfile = require('child_process');
    var request = require('request');
    
    router.get('/', function(req, res, next) {
        var imgUrl = url.parse(req.url, true).query.url;
        console.log(url.parse(req.url,true).query); 
    
        console.log('get a request for ' + imgUrl);
        if (imgUrl == null || imgUrl == "" || imgUrl == undefined) {
            console.log('end');
            res.end();
            return;
        }
    
        var parsedUrl = url.parse(imgUrl);
        // 这里暂时使用图片服务器主机名做Referer
        var referrer = parsedUrl.protocol + '//' + parsedUrl.host; 
        console.log('referrer ' + referrer);
    
        var options = {
          uri: imgUrl,
          headers: {
             'Referer': referrer
          }
        };
    
        function callback(error, response, body) {
          if (!error && response.statusCode == 200) {
            console.log("type " + response.headers['content-type']);
          }
          res.end(response.body);
        }
    
        // request(options, callback);
        request(options)
            .on('error', function(err) {
                console.log(err)
            })
            .pipe(res);
    });
    
    module.exports = router;

2.5 优化

这部分主要是防盗链部分的优化。

单就 Referer 来说,使用空值和主机名都只能满足部分需求。

一个优化方式是组合,当一种方式不能突破即采用另一种方式。
这种方式的有点在于扩大了适用面积,并且方法对任何场景比较通用。

一个优化方式是接口请求参数带源引用连接。
这种方式对很多人来说不太通用,因为很多场景下并不清楚源引用连接在哪。
但是对我的插件来说非常适用,插件本身保留了源引用。因此可以很好的绕过防盗链限制。

2017/2/7 posted in  web开发

使用LeanCloud服务做一站式Chrome插件开发——Favorite Image

0. 目录

  1. 要开发的是什么项目 1.1 想法开端 1.2 应该有什么功能?
  2. 开发需要解决的核心问题
  3. 具体解决方案 3.1 帐号系统 3.2 存储服务 3.3 使用LeanEngine做反防盗链中转接口 3.4 Chrome 插件实现
  4. 对去后端化的看法

1. 要开发的是什么项目?

一个Chrome插件,用来保存浏览网页时看到的喜欢的图片。

1.1 想法开端

在 pixiv 翻图时看到一些喜欢的插画,看完就随手翻过去了,没有保存。为什么呢? 因为以我对自己的了解,图片下载下来,就相当于放进了垃圾桶。 并不是因为本地的文件管理有多乱,而是因为,几乎没有用鼠标打开文件管理器的习惯。

现在我获取信息的流量入口最常用的只有两个:1. 终端 2. 浏览器

于是乎,一个想法油然而生:

把插画存到浏览器吧!

于是就立刻构思,动手写了这款插件。

1.2 应该有什么功能?

功能很简单,
保存操作:1. 对图片点击右键 2. 选择"保存到浏览器.." 之类的选项
查看操作:1. 点击插件图标 查看保存过的图片。
其它:1. 图片同步到云端,也可保存到浏览器本地。2. 既然要保存到云端,自然需要账号系统

2. 开发需要解决的核心问题

核心问题有两个,一个是数据云存储问题,一个是图片防盗链问题。

云存储问题,帐号系统,多端同步
最开始只想做浏览器本地的存储,使用Chrome提供的localStorage存在本地就。
后来因为localStorage并不支持数据库语法查询,有很多不便。使用过程中又发现多端同步在体验上的优越性,决定要把存储放到云上。

图片防盗链问题
看了些资料,解决方式基本可以分为两种。

一类使用前端js嵌入iframe解决,优点是解决方式简单,问题是Chrome插件不支持页面嵌入式的js脚本。所以这个方案pass。

第二类使用后台服务器做反防盗链措施,作为中转给前端使用。优点是不受chrome插件的各种安全机制的限制,缺点是需要后台支持,增加工作量和资源成本。
使用第二类完成。

3. 具体解决方案

云存储及帐号系统使用LeanCloud提供的存储服务解决。
反防盗链接口使用LeanCloud提供的云引擎搭建NodeJs后台。

啰嗦一句,为什么要使用LeanCloud?
一是对我的需求可以做到完全免费,二是它们的文档实在是太xx的好用了。

3.1 帐号系统

参照:数据存储入门教程 · JavaScript

实现过程基本照抄这个教程的代码。后台账号系统包括对账号的重复检测、密码加密、session等都已经实现。

我们要做的,就是调用前端的这几个关键方法,实现简单的注册、登陆、退出:

  // LeanCloud - 注册
  // https://leancloud.cn/docs/leanstorage_guide-js.html#注册
  var user = new AV.User();
  user.setUsername(username);
  user.setPassword(password);
  user.setEmail(email);
  user.signUp().then(function (loginedUser) {
    // 注册成功
  }, (function (error) {
      alert(JSON.stringify(error));
  }));


  // LeanCloud - 登录
  // https://leancloud.cn/docs/leanstorage_guide-js.html#用户名和密码登录
  AV.User.logIn(username, password).then(function (loginedUser) {
    // 登录成功
  }, function (error) {
    alert(JSON.stringify(error));
  });


  // LeanCloud - 当前用户信息
  // https://leancloud.cn/docs/leanstorage_guide-js.html#当前用户
  var currentUser = AV.User.current();


  // 退出登陆
  AV.User.logOut();

3.2 存储服务

使用账号系统为每个用户添加身份信息后,存储部分就只需要把数据 + 用户身份信息一同上传或下载就可以了。

照样只贴关键方法

// 初始化类(在数据库中表现为数据表`ImageRepo`)和实例(数据库中表现为一条数据)
this.ImageRepo = AV.Object.extend('ImageRepo');
var repo = new this.ImageRepo();
// 填充数据
repo.put('username', 'xxx');
// 上传数据
repo.save().then(function (repo) {
    }, function (error) {
    });

// 下载数据
// 初始化对'ImageRepo'表的查询
var query = new AV.Query('ImageRepo');
// 查询条件为 username字段等于'xxx'
query.equalTo('username', 'xxx');
// 查询
query.find().then(function(results) {
    // 遍历results
    // 在页面添加解决防盗链问题后的图片
}, function(error) {
});

3.3 使用LeanEngine做反防盗链中转接口

要实现的效果是:
1. 我有一个防盗链图片连接abc.com/xxx.png
2. 我的接口url是http://codeli.leanapp.cn/image?url=xxx
3. 访问http://codeli.leanapp.cn/image?url=abc.com/xxx.png可访问原图,不受防盗链措施限制。

主要原理很简单,后台处理图片请求时更改header中的referer字段,将结果作为response返回。

关于这部分的实现,欢迎阅读我的另一篇文章,就不再赘述:
服务器作防盗链图片中转,nodejs 上手项目简明教程

关于LeanEngin的使用,文档如下,使用方法非常简单。
云引擎快速入门

云引擎支持NodeJS Python PHP JAVA
只需要下载云引擎命令行工具lean,然后输入几行命令就可以建立一个你熟悉的web框架。
然后,使用你熟悉的语言编写反防盗链实现就行了。

3.4 Chrome 插件实现

有了 3.1~3.3 的实现,这部分就是简单的插件部署和业务逻辑了。

Chrome 插件结构如图:
图片来自 蒋国纲的技术博客

主要业务:
1. 在popup窗口中添加注册 登陆 退出 等业务。
2. 打开popup 窗口时从云端获取指定账号下保存的图片信息,并展示。若未登陆,则从浏览器localStorage获取并展示。
3. background script 中添加右键菜单项: 当目标是图片时,显示Keep image in browser
4. 点击Keep image in browser, 执行保存业务逻辑: 若登陆了,保存到云端。若未登录,保存到浏览器localStorage

具体实现见我的github项目: KeepImageInBrowser
插件Web Store地址: Favorite Image

4. 最后,对去后端化的看法

前段时间在知乎上看到了一个问题,我也顺便说下自己的看法。

web后端会不会变得越来越不需要?

像bmob和leancloud这类后台云服务的流行有一段日子了,使用这些服务使一些web、app的开发周期大大缩减。这对于小团队和初创公司尤为方便。

但这并不意味着不再需要自己开发后台。不是因为他们提供的服务不够全面(相反,我倒认为这类服务将向着全面、便捷、快速发展),而是因为很多公司和产品,为了保持服务的质量和稳定,突出自己产品的特性,需要自己定制自己的后台,有针对性的去优化某些模块。
云服务作为大众服务平台难以为每个产品做定制。

类似于游戏引擎,如今各个平台都不缺乏优秀的游戏引擎。可是仍有公司和团队耗费大量的成本自研游戏引擎,就是希望能配合自己的游戏系统,完美地展现自己的游戏。

一样的,后台云服务和自定制的后台,是相交但永远不会重合的关系。 他们彼此之间相互影响,共同进步。

2017/2/7 posted in  web开发