惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
T
Threatpost
Google DeepMind News
Google DeepMind News
WordPress大学
WordPress大学
Recorded Future
Recorded Future
小众软件
小众软件
N
Netflix TechBlog - Medium
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
博客园 - 聂微东
美团技术团队
F
Fortinet All Blogs
I
InfoQ
U
Unit 42
酷 壳 – CoolShell
酷 壳 – CoolShell
阮一峰的网络日志
阮一峰的网络日志
The Cloudflare Blog
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
Vercel News
Vercel News
GbyAI
GbyAI
有赞技术团队
有赞技术团队
博客园 - 【当耐特】
H
Help Net Security
B
Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
Jina AI
Jina AI
The Register - Security
The Register - Security
Hugging Face - Blog
Hugging Face - Blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 叶小钗
Recent Announcements
Recent Announcements
D
DataBreaches.Net
IT之家
IT之家
雷峰网
雷峰网
Y
Y Combinator Blog
W
WeLiveSecurity
P
Proofpoint News Feed
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
量子位
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 司徒正美
月光博客
月光博客
The Hacker News
The Hacker News

程序员笔记(huoyijie.cn)

generate new dataset with llm and knowledge graph resume deepseek applications Implement the classic huarongdao game with react Implement the classic tetris game with react Writing admin dashboard template with Nextjs How to AuthN user with OpenLDAP share whiteboard with websocket ocr overview tech notes subscribe topic with sse multi devices github an algorithm used for image text detect an algorithm used for image text detect facial expression recognition technology based on neural network oauth2 mysql store a real time collaborative whiteboard with infinite space How to perform load testing with Grafana k6 fix split of undefined with gitbook theme nodejs http proxy middleware analyze referer of express website writing a CDN origin server with Django a personal lightweight CMS tool writing in Golang profile introduce the post processing introduce the text detect method blockchain for dummies medical applications on ai facial expression recognition study facial expression recognition auth auth with oauth2 auth with jwt chat with sse react chat with websocket react http message signatures infinite whiteboard with socket.io multi factor auth with totp subscribe topic with sse capture screen to gif Golang 经典入门实践教程 · GitBook EFS 加密文件系统 · GitBook 弈杰围棋智能终端 · GitBook 如何把手机触摸屏变成触控板(Touchpad) · GitBook 基于 SOCKET.IO 搭建个人电脑 HTTP 静态服务器代理 · GitBook 三百行代码搭建一个简单的 SOCKS5 代理服务器 · GitBook 微信小程序(蓝牙BLE)远程遥控树莓派小车 · GitBook 语音远程遥控树莓派小车 · GitBook 树莓派的一些使用经验 · GitBook 从零开始制作树莓派小车 · GitBook UDPack 2.0 协议 · GitBook 前言 · GitBook 升级 Ubuntu 到 20.04 LTS 长期维护版本 · GitBook 反向代理服务器实现原理 · GitBook 网站支持 HTTP 2.0 · GitBook 搭建配置邮件服务器 · GitBook 使用 Git 与 Gitbook 创建管理电子书 · GitBook 网站图片视频接入CDN · GitBook a personal lightweight self hosted git server written in Golang 基于 Git 搭建代码托管服务器 · GitBook
快速搭建CDN回源服务器 · GitBook
huoyijie · 2024-05-24 · via 程序员笔记(huoyijie.cn)

本文接上篇 网站图片视频接入CDN,上篇文章介绍了如何把图片接入 CDN,里面提到了需要搭建自有的回源服务器。本文将会介绍如何快速搭建一个简单的回源服务器。我的服务器是 Ubuntu 系统,后面的操作都是基于 Ubuntu。

回源服务器只需提供静态资源访问,所以有很多工具都可以实现。我选择了 nodejs+express,先检查下回源服务器环境。

$ node -v
v8.10.0
$ npm -v
6.14.4

全局安装 express

$ sudo npm install express --save -g
$ sudo npm install express-generator --save -g

创建项目

$ express cdn-service
# 控制台输出
# create : cdn-service/
# create : cdn-service/public/
# create : cdn-service/public/javascripts/
# create : cdn-service/public/images/
# create : cdn-service/public/stylesheets/
# create : cdn-service/public/stylesheets/style.css
# create : cdn-service/routes/
# create : cdn-service/routes/index.js
# create : cdn-service/routes/users.js
# create : cdn-service/views/
# create : cdn-service/views/error.jade
# create : cdn-service/views/index.jade
# create : cdn-service/views/layout.jade
# create : cdn-service/app.js
# create : cdn-service/package.json
# create : cdn-service/bin/
# create : cdn-service/bin/www

$ cd cdn-service
# 安装依赖
$ npm install
# 运行 App
$ DEBUG=cdn-service:* npm start

express 脚手架已经生成了项目结构。看一下程序入口 cdn-service/app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

下面这个语句配置了可以公开访问的静态文件的存储目录,后面会在该目录下建子目录用来存放静态资源

app.use(express.static(path.join(__dirname, 'public')));

因为只对外提供静态文件访问,可以删除掉下面几行代码

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

app.use('/', indexRouter);
app.use('/users', usersRouter);

此时除了访问已上传的静态资源之外,其他路径都会返回 404,并显示出错页面。然后修改下出错后渲染的页面,删除所有内容,只显示一张大图,显示更加友好。

404

建立静态资源存放目录ab

$ cd cdn-service/public
$ mkdir -p ab/static

Copy 一张测试图片放到 ab/static 下,我选的就是上面显示的那张 404 图 404.svg

配置 cdn-service 服务开机自启动,输入命令

$ crontab -e

编辑加入下面一行配置,其中 path 填入服务器上实际的部署路径

@reboot cd ${path}/cdn-service; sudo npm start > console.log 2>&1;

修改下启动端口,编辑 bin/www,把 4000 改成想要的端口

var port = normalizePort(process.env.PORT || '4000');

现在来测试下,手动启动 cdn-service 服务

$ sudo npm start > console.log 2>&1 &

打开浏览器访问任何一个不存在的路径都会显示刚刚的 404 图片。到目前为指,回源服务已经搭建好了。现在去到腾讯云 CDN 控制台,去配置回源服务器站点

回源服务器配置

如上图,回源协议选择 HTTP,源站地址填写 ip:port,其中 ip 是回源服务器地址,端口就是刚刚配置的端口号,现在通过 cdn 加速域名访问一下刚刚的 404 图片(https://cdn.huoyijie.cn/ab/static/404.svg) 试试,已经可以正常访问了,并且经过初次访问预热后,CDN 格节点已经缓存了该图片。

接下来还有一个重要问题,就是如何快速方便的把资源文件上传到回源服务器呢?我写了一个 nodejs 脚本,可以方便在自己的电脑上面管理源服务器上面的资源。

$ ./scripts/cdnclient.js 
Usage: node cdnclient [--cmd=nEW,pUBLISH,delete,hELP] [--id or --articleId=article uuid] [--fILE=file] [--isdir=false]

cdnclient主要有如下命令:

1.创建新目录

./scripts/cdnclient.js --cmd=new --id=${dir.name}

--cmd=new 代表创建命令,--id=${dir.name} 传入目录名,创建好目录后,后续可以在该目录下上传资源文件。我一般会传入 articleId(uuid) 作为目录名

2.上传文件

./scripts/cdnclient.js  --cmd=publish --id=fa1b6d507d7111eb9aeae3d030369a53 --file=~/cdnservice-cover.jpg

--cmd=publish 代表上传文件命令,--id=fa1b6d507d7111eb9aeae3d030369a53 是上传的目标目录,--file=~/cdnservice-cover.jpg 是要上传的文件名,命令执行完成后,文件会被同步到源服务器指定目录下。

3.删除文件

./scripts/cdnclient.js  --cmd=delete --id=fa1b6d507d7111eb9aeae3d030369a53 --file=cdnservice-cover.jpg

--cmd=delete 代表删除命令,删除--id=fa1b6d507d7111eb9aeae3d030369a53 目录下的--file=cdnservice-cover.jpg 文件

4.删除目录

./scripts/cdnclient.js  --cmd=delete --id=fa1b6d507d7111eb9aeae3d030369a53 --isdir=true

--cmd=delete 代表删除命令,--isdir=true 表示要删除--id=fa1b6d507d7111eb9aeae3d030369a53 的目录

5.help 信息

./scripts/cdnclient.js  --cmd=help

cdnclient 脚本底层是基于 ssh 命令实现的,运行这个脚本可以直接 ssh 连接远程服务器进行数据传输。Ubuntu 系统的 ssh 密钥信息一般存储在 ~/.ssh 目录下。

$ ls ~/.ssh/
authorized_keys  id_rsa.pub
id_rsa           known_hosts

其中 id_rsa 是当前主机的私钥文件,绝对不能通过任何途径传给任何人,这个要保管好。id_rsa.pub 是当前主机的公钥文件。authorized_keys是授权文件,可配置谁能通过公钥免密码登录当前主机,是影响主机安全的非常重要的文件。

我把自己电脑的公钥 id_rsa.pub 加入到了回源服务器的授权文件 authorized_keys中,这样我的电脑就可以运行上面的脚本了。运行这个脚本需要先安装 shelljs npm 包。

npm i shelljs --save

最后附上 cdnclient.js 脚本

#!/usr/bin/env node

const fs = require('fs')
const shell = require('shelljs')

// 命令行参数
const args = require('minimist')(process.argv.slice(2))
const cmd = args['cmd'] || 'help'

const cdnDir = '${work.dir}/cdn-service/public'
const abCdnDir = `${cdnDir}/ab`

if (cmd === 'n' || cmd === 'new') {
    const nHelpTxt = `--cmd=nEW --id=articleId`
    const articleId = args['id'] || args['articleId']
    if (!articleId) {
        console.error(`lack of articleId param with cmd -> ${cmd}`)
        console.log(nHelpTxt)
        process.exit(-1)
    }

    const articleCdnDir = `${abCdnDir}/${articleId}`
    if (!shell.exec(`ssh -o ServerAliveInterval=60 ${user.name}@${回源服务器域名或ip} 'mkdir ${articleCdnDir}'`).code) {
        console.log(`new article cdn dir succeed with id -> ${articleId}`)
        process.exit(0)
    } else {
        console.error(`error: mkdir article cdn dir failed with id -> ${articleId}`)
        process.exit(-1)
    }
}

if (cmd === 'p' || cmd === 'publish') {
    const pHelpTxt = `--cmd=pUBLISH --id=articleId --fILE=files`
    const articleId = args['id'] || args['articleId']
    if (!articleId) {
        console.error(`lack of articleId param with cmd -> ${cmd}`)
        console.log(pHelpTxt)
        process.exit(-1)
    }

    const files = args['f'] || args['file']
    if (!files) {
        console.error(`lack of file param with cmd -> ${cmd}`)
        console.log(pHelpTxt)
        process.exit(-1)
    }

    const articleCdnDir = `${abCdnDir}/${articleId}`
    if (!shell.exec(`scp ${files} ${user.name}@${回源服务器域名或ip}:${articleCdnDir}/`).code) {
        console.log(`publish files to cdn succeed with id -> ${articleId} and file -> ${files}`)
        process.exit(0)
    } else {
        console.error(`error: publish file to cdn failed with id -> ${articleId} and file -> ${files}`)
        process.exit(-1)
    }
}

if (cmd === 'delete') {
    const delHelpTxt = `--cmd=delete --id=articleId --isdir=false --fILE=files`
    const articleId = args['id'] || args['articleId']
    if (!articleId) {
        console.error(`lack of articleId param with cmd -> ${cmd}`)
        console.log(delHelpTxt)
        process.exit(-1)
    }

    const isDir = args['isdir'] || 'false'
    if (isDir === 'true') {
        const articleCdnDir = `${abCdnDir}/${articleId}`
        if (!shell.exec(`ssh -o ServerAliveInterval=60 ${user.name}@${回源服务器域名或ip} 'rm -rf ${articleCdnDir}'`).code) {
            console.log(`delete dir from cdn succeed with id -> ${articleId}`)
            process.exit(0)
        } else {
            console.error(`error: delete dir from cdn failed with id -> ${articleId}`)
            process.exit(-1)
        }
    }

    const files = args['f'] || args['file']
    if (!files) {
        console.error(`lack of file param with cmd -> ${cmd}`)
        console.log(delHelpTxt)
        process.exit(-1)
    }

    const articleCdnDir = `${abCdnDir}/${articleId}`
    if (!shell.exec(`ssh -o ServerAliveInterval=60 ${user.name}@${回源服务器域名或ip} 'rm ${articleCdnDir}/${files}'`).code) {
        console.log(`delete files from cdn succeed with id -> ${articleId} and file -> ${files}`)
        process.exit(0)
    } else {
        console.error(`error: delete file from cdn failed with id -> ${articleId} and file -> ${files}`)
        process.exit(-1)
    }
}

const helpTxt = `Usage: node cdnclient [--cmd=nEW,pUBLISH,delete,hELP] [--id or --articleId=article uuid] [--fILE=file] [--isdir=false]`

if (cmd === 'h' || cmd === 'help') {
  console.log(helpTxt)
  process.exit(0)
}

console.error(`unknown cmd -> ${cmd}`)
console.log(helpTxt)

process.exit(-1)

注意其中有一些环境变量,需要根据实际情况进行配置

const cdnDir = '${work.dir}/cdn-service/public'
const abCdnDir = `${cdnDir}/ab`

${work.dir} 是工作目录,也就是源站服务器中,cd-service 的部署目录,/ab 目录是为博客项目分配的一个顶层上级目录,这个是可以根据实际情况设置的。${user.name}@${回源服务器域名或ip} 是回源服务器的用户名和 ip/域名。

新建文章前,可以先传入 articleId 并通过 cdnclient 命令创建文章资源目录,遇到需要引用资源时,可以通过 cdnclient 命令把相关资源上传至源服务器,拿到加速域名 (cdn.huoyijie.cn) 可以引用到的 url 地址,然后在文章中引用这个 url 即可。

OK,关于搭建 CDN 回源服务器的主要内容就介绍完了。