日常开发过程中,我们经常需要修改一些放在 CDN 上的静态文件(如 JavaScript、CSS、HTML 文件等),这个过程中,我们希望能有一种方式将线上 CDN 的目录映射为本地硬盘上的某个目录,这样,当我们在本地修改了某个文件时,不需要发布,刷新后马上能看到效果。
比如,我们的 CDN 域名是:http://a.mycdn.com,本地对应的目录是:D:\workassets,我们希望所有对 http://a.mycdn.com/* 的访问被映射到本地的 D:\workassets\* 下。如访问 http://a.mycdn.com/s/atp.js 时,实际上是读取的是本地的 D:\workassetss\atp.js,而不需要从网上下载线上的文件。
实现这个功能很简单,关键点如下:
1、在本地开启一个 HTTP 服务,监听 80 端口;
2、修改系统 hosts 文件,添加“127.0.0.1 a.mycdn.com”,将 CDN 域名绑定为本地服务器地址;
3、配置本地 HTTP 服务,接收到一个 GET 请求后,先检查本地硬盘上是否存在对应的文件,如存在,则返回这个文件的内容,如不存在,则返回线上对应的内容。
可以看到,关键部分是需要搭建一个本地的 HTTP 服务。这方面有很多教程,比如在本地安装 Apache 或 Ngnix 等服务器软件,再配置相应的转发规则等。不过个人觉得这类方法还是有点复杂,本文要介绍的,是另外的不需要安装服务器软件的方法。
因为我们是在本地开发调试,对性能、并发性的要求并不高,因此我们其实并不需要一个像 Apache/Ngnix 这样的专业的 HTTP 软件,我们只需要一段能提供 HTTP 服务的脚本即可。比如用 nodejs 来实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
/**
* author: oldj
* blog: http://oldj.net
*
**/
var
http
=
require
(
"http"
)
,
url
=
require
(
"url"
)
,
path
=
require
(
"path"
)
,
fs
=
require
(
"fs"
)
,
local_folders
,
base_url
;
local_folders
=
[
// 本地路径,代理将在这个列表中的目录下寻找文件,如果没有找到则转到线上地址
"D:/work/assets"
]
;
base_url
=
"http://10.232.133.214"
;
// 线上路径,如果找不到文件,则转向到这个地址
function
loadFile
(
pathname
,
response
)
{
var
i
,
l
=
local_folders
.
length
,
fn
;
console
.
log
(
"try to load "
+
pathname
)
;
for
(
i
=
0
;
i
<
l
;
i
++
)
{
fn
=
local_folders
[
i
]
+
pathname
;
if
(
path
.
existsSync
(
fn
)
&&
fs
.
statSync
(
fn
)
.
isFile
(
)
)
{
fs
.
readFile
(
fn
,
function
(
err
,
data
)
{
response
.
writeHead
(
200
)
;
response
.
write
(
data
)
;
response
.
end
(
)
;
}
)
;
return
;
}
}
response
.
writeHead
(
302
,
{
"Location"
:
base_url
+
pathname
}
)
;
response
.
end
(
)
;
}
http
.
createServer
(
function
(
request
,
response
)
{
var
req_url
=
request
.
url
,
pathname
;
// 处理类似 http://a.tbcdn.cn/??p/global/1.0/global-min.css,tbsp/tbsp.css?t=20110920172000.css 的请求
pathname
=
req_url
.
indexOf
(
"??"
)
==
-
1
?
url
.
parse
(
request
.
url
)
.
pathname
:
req_url
;
console
.
log
(
"Request for '"
+
pathname
+
"' received."
)
;
loadFile
(
pathname
,
response
)
;
}
)
.
listen
(
80
)
;
|
注意将上面的 local_folders 和 base_url 两个变量的值修改为你需要的值。将这个文件保存下来,比如保存为 local-cdn-proxy.js,然后在命令行里执行“node local-cdn-proxy.js”,本地服务器就运行起来了,当然,别忘了绑定 hosts 。
当通过 http 访问一个路径时,上面的脚本会先在本地对应的目录下查找,找到则返回对应文件的内容,找不到则直接 302 跳转到线上对应的地址。对于找不到的情况,还有一种处理办法是由本地服务器从线上下载对应的内容并返回,不过对这个需求来说,302 跳转就足够了。
除了 nodejs 版本,我也写了一个 Python 的版本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
# -*- coding: utf-8 -*-
#
# author: oldj
# blog: http://oldj.net
#
import
os
import
BaseHTTPServer
LOCAL_FOLDERS
=
[
"D:/work/assets"
]
BASE_URL
=
"http://10.232.133.214"
class
WebRequestHandler
(
BaseHTTPServer
.
BaseHTTPRequestHandler
)
:
def
do_GET
(
self
)
:
print
"Request for '%s' received."
%
self
.
path
for
folder
in
LOCAL_FOLDERS
:
fn
=
os.path
.
join
(
folder
,
self
.
path
.
replace
(
"/"
,
os
.
sep
)
[
1
:
]
)
if
os.path
.
isfile
(
fn
)
:
self
.
send_response
(
200
)
self
.
wfile
.
write
(
open
(
fn
,
"rb"
)
.
read
(
)
)
break
else
:
self
.
send_response
(
302
)
self
.
send_header
(
"Location"
,
"%s%s"
%
(
BASE_URL
,
self
.
path
)
)
server
=
BaseHTTPServer
.
HTTPServer
(
(
"0.0.0.0"
,
80
)
,
WebRequestHandler
)
server
.
serve_forever
(
)
|
可以看到,Python 版本的代码比 nodejs 版本的精简了很多。
上面的两段代码的功能还相对比较简单,比如没有输出内容的 MIME-Type、Content-Length 等头信息,对可能的阻塞操作(如读取文件超时等)也没有做特别的处理。对于本地开发环境来说,它们已经是可以工作的版本了,你也可以继续扩展这两个脚本,以便满足更多的需求。