使用Node.js搭建静态资源服务器

使用Node.js搭建静态资源服务器,第1张

对于Node.js新手,搭建一个静态资源服务器是个不错的锻炼,从最简单的返回文件或错误开始,渐进增强,还可以逐步加深对http的理解。那就开始吧,让我们的双手沾满网络请求!

Note:

当然在项目中如果有使用express框架,用express.static一行代码就可以达到目的了:

  1. app.use(express.static('public'))

这里我们要实现的正是express.static背后所做工作的一部分,建议同步阅读该模块源码。

基本功能

不急着写下第一行代码,而是先梳理一下就基本功能而言有哪些步骤。

  1. 在本地根据指定端口启动一个http server,等待着来自客户端的请求
  2. 当请求抵达时,根据请求的url,以设置的静态文件目录为base,映射得到文件位置
  3. 检查文件是否存在
  4. 如果文件不存在,返回404状态码,发送not found页面到客户端
  5. 如果文件存在:
    • 打开文件待读取
    • 设置response header
    • 发送文件到客户端
  6. 等待来自客户端的下一个请求

实现基本功能

代码结构

创建一个nodejs-static-webserver目录,在目录内运行npm init初始化一个package.json文件。

  1. mkdir nodejs-static-webserver && cd "$_"
  2. // initialize package.json
  3. npm init

接着创建如下文件目录:

  1. -- config
  2. ---- default.json
  3. -- static-server.js
  4. -- app.js

default.json

  1. {
  2. "port": 9527,
  3. "root": "/Users/sheila1227/Public",
  4. "indexPage": "index.html"
  5. }

default.js存放一些默认配置,比如端口号、静态文件目录(root)、默认页(indexPage)等。当这样的一个请求http://localhost:9527/myfiles/抵达时. 如果根据root映射后得到的目录内有index.html,根据我们的默认配置,就会给客户端发回index.html的内容。

static-server.js

  1. const http = require('http');
  2. const path = require('path');
  3. const config = require('./config/default');

  4. class StaticServer {
  5. constructor() {
  6. this.port = config.port;
  7. this.root = config.root;
  8. this.indexPage = config.indexPage;
  9. }

  10. start() {
  11. http.createServer((req, res) => {
  12. const pathName = path.join(this.root, path.normalize(req.url));
  13. res.writeHead(200);
  14. res.end(`Requeste path: ${pathName}`);
  15. }).listen(this.port, err => {
  16. if (err) {
  17. console.error(err);
  18. console.info('Failed to start server');
  19. } else {
  20. console.info(`Server started on port ${this.port}`);
  21. }
  22. });
  23. }
  24. }

  25. module.exports = StaticServer;

在这个模块文件内,我们声明了一个StaticServer 类,并给其定义了start方法,在该方法体内,创建了一个server对象,监听rquest事件,并将服务器绑定到配置文件指定的端口。在这个阶段,我们对于任何请求都暂时不作区分地简单地返回请求的文件路径。path模块用来规范化连接和解析路径,这样我们就不用特意来处理操作系统间的差异。

app.js

  1. const StaticServer = require('./static-server');

  2. (new StaticServer()).start();

在这个文件内,调用上面的static-server模块,并创建一个StaticServer实例,调用其start方法,启动了一个静态资源服务器。这个文件后面将不需要做其他修改,所有对静态资源服务器的完善都发生在static-server.js内。

在目录下启动程序会看到成功启动的log:

  1. > node app.js

  2. Server started on port 9527

在浏览器中访问,可以看到服务器将请求路径直接返回了。

使用Node.js搭建静态资源服务器,第2张

路由处理

之前我们对任何请求都只是向客户端返回文件位置而已,现在我们将其替换成返回真正的文件:

  1. routeHandler(pathName, req, res) {

  2. }

  3. start() {
  4. http.createServer((req, res) => {
  5. const pathName = path.join(this.root, path.normalize(req.url));
  6. this.routeHandler(pathName, req, res);
  7. }).listen(this.port, err => {
  8. ...
  9. });
  10. }

将由routeHandler来处理文件发送。

读取静态文件

读取文件之前,用fs.stat检测文件是否存在,如果文件不存在,回调函数会接收到错误,发送404响应。

  1. respondNotFound(req, res) {
  2. res.writeHead(404, {
  3. 'Content-Type': 'text/html'
  4. });
  5. res.end(`<h1>Not Found</h1><p>The requested URL ${req.url} was not found on this server.</p>`);
  6. }

  7. respondFile(pathName, req, res) {
  8. const readStream = fs.createReadStream(pathName);
  9. readStream.pipe(res);
  10. }

  11. routeHandler(pathName, req, res) {
  12. fs.stat(pathName, (err, stat) => {
  13. if (!err) {
  14. this.respondFile(pathName, req, res);
  15. } else {
  16. this.respondNotFound(req, res);
  17. }
  18. });
  19. }

Note:

读取文件,这里用的是流的形式createReadStream而不是readFile,是因为后者会在得到完整文件内容之前将其先读到内存里。这样万一文件很大,再遇上多个请求同时访问,readFile就承受不来了。使用文件可读流,服务端不用等到数据完全加载到内存再发回给客户端,而是一边读一边发送分块响应。这时响应里会包含如下响应头:

  1. Transfer-Encoding:chunked

默认情况下,可读流结束时,可写流的end()方法会被调用。

MIME支持

现在给客户端返回文件时,我们并没有指定Content-Type头,虽然你可能发现访问文本或图片浏览器都可以正确显示出文字或图片,但这并不符合规范。任何包含实体主体(entity body)的响应都应在头部指明文件类型,否则浏览器无从得知类型时,就会自行猜测(从文件内容以及url中寻找可能的扩展名)。响应如指定了错误的类型也会导致内容的错乱显示,如明明返回的是一张jpeg图片,却错误指定了header:'Content-Type': 'text/html',会收到一堆乱码。

使用Node.js搭建静态资源服务器,第3张

虽然有现成的mime模块可用,这里还是自己来实现吧,试图对这个过程有更清晰的理解。

在根目录下创建mime.js文件:

  1. const path = require('path');

  2. const mimeTypes = {
  3. "css": "text/css",
  4. "gif": "image/gif",
  5. "html": "text/html",
  6. "ico": "image/x-icon",
  7. "jpeg": "image/jpeg",
  8. ...
  9. };

  10. const lookup = (pathName) => {
  11. let ext = path.extname(pathName);
  12. ext = ext.split('.').pop();
  13. return mimeTypes[ext] || mimeTypes['txt'];
  14. }

  15. module.exports = {
  16. lookup
  17. };

该模块暴露出一个lookup方法,可以根据路径名返回正确的类型,类型以'type/subtype’表示。对于未知的类型,按普通文本处理。

接着在static-server.js中引入上面的mime模块,给返回文件的响应都加上正确的头部字段:

  1. respondFile(pathName, req, res) {
  2. const readStream = fs.createReadStream(pathName);
  3. res.setHeader('Content-Type', mime.lookup(pathName));
  4. readStream.pipe(res);
  5. }

重新运行程序,会看到图片可以在浏览器中正常显示了。

使用Node.js搭建静态资源服务器,第4张

Note:

需要注意的是,Content-Type说明的应是原始实体主体的文件类型。即使实体经过内容编码(如gzip,后面会提到),该字段说明的仍应是编码前的实体主体的类型。

添加其他功能

至此,已经完成了基本功能中列出的几个步骤,但依然有很多需要改进的地方,比如如果用户输入的url对应的是磁盘上的一个目录怎么办?还有,现在对于同一个文件(从未更改过)的多次请求,服务端都是勤勤恳恳地一遍遍地发送回同样的文件,这些冗余的数据传输,既消耗了带宽,也给服务器添加了负担。另外,服务器如果在发送内容之前能对其进行压缩,也有助于减少传输时间。

读取文件目录

现阶段,用url: localhost:9527/testfolder去访问一个指定root文件夹下真实存在的testfolder的文件夹,服务端会报错:

  1. Error: EISDIR: illegal operation on a directory, read

要增添对目录访问的支持,我们重新整理下响应的步骤:

  1. 请求抵达时,首先判断url是否有尾部斜杠
  2. 如果有尾部斜杠,认为用户请求的是目录
    • 如果目录存在
      • 如果目录下存在默认页(如index.html),发送默认页
      • 如果不存在默认页,发送目录下内容列表
    • 如果目录不存在,返回404
  3. 如果没有尾部斜杠,认为用户请求的是文件
    • 如果文件存在,发送文件
    • 如果文件不存在,判断同名的目录是否存在
      • 如果存在该目录,返回301,并在原url上添加上/作为要转到的location
      • 如果不存在该目录,返回404

我们需要重写一下routeHandler内的逻辑:

  1. routeHandler(pathName, req, res) {
  2. fs.stat(pathName, (err, stat) => {
  3. if (!err) {
  4. const requestedPath = url.parse(req.url).pathname;
  5. if (hasTrailingSlash(requestedPath) && stat.isDirectory()) {
  6. this.respondDirectory(pathName, req, res);
  7. } else if (stat.isDirectory()) {
  8. this.respondRedirect(req, res);
  9. } else {
  10. this.respondFile(pathName, req, res);
  11. }
  12. } else {
  13. this.respondNotFound(req, res);
  14. }
  15. });
  16. }

继续补充respondRedirect方法:

  1. respondRedirect(req, res) {
  2. const location = req.url + '/';
  3. res.writeHead(301, {
  4. 'Location': location,
  5. 'Content-Type': 'text/html'
  6. });
  7. res.end(`Redirecting to <a href='${location}'>${location}</a>`);
  8. }

浏览器收到301响应时,会根据头部指定的location字段值,向服务器发出一个新的请求。

继续补充respondDirectory方法:

  1. respondDirectory(pathName, req, res) {
  2. const indexPagePath = path.join(pathName, this.indexPage);
  3. if (fs.existsSync(indexPagePath)) {
  4. this.respondFile(indexPagePath, req, res);
  5. } else {
  6. fs.readdir(pathName, (err, files) => {
  7. if (err) {
  8. res.writeHead(500);
  9. return res.end(err);
  10. }
  11. const requestPath = url.parse(req.url).pathname;
  12. let content = `<h1>Index of ${requestPath}</h1>`;
  13. files.forEach(file => {
  14. let itemLink = path.join(requestPath,file);
  15. const stat = fs.statSync(path.join(pathName, file));
  16. if (stat && stat.isDirectory()) {
  17. itemLink = path.join(itemLink, '/');
  18. }
  19. content += `<p><a href='${itemLink}'>${file}</a></p>`;
  20. });
  21. res.writeHead(200, {
  22. 'Content-Type': 'text/html'
  23. });
  24. res.end(content);
  25. });
  26. }
  27. }

当需要返回目录列表时,遍历所有内容,并为每项创建一个link,作为返回文档的一部分。需要注意的是,对于子目录的href,额外添加一个尾部斜杠,这样可以避免访问子目录时的又一次重定向。

在浏览器中测试一下,输入localhost:9527/testfolder,指定的root目录下并没有名为testfolder的文件,却存在同名目录,因此第一次会收到重定向响应,并发起一个对目录的新请求。

使用Node.js搭建静态资源服务器,第5张

缓存支持

为了减少数据传输,减少请求数,继续添加缓存支持。首先梳理一下缓存的处理流程:

  1. 如果是第一次访问,请求报文首部不会包含相关字段,服务端在发送文件前做如下处理:

    • 如服务器支持ETag,设置ETag
    • 如服务器支持Last-Modified,设置Last-Modified
    • 设置Expires
    • 设置Cache-Control头(设置其max-age值)

    浏览器收到响应后会存下这些标记,并在下次请求时带上与ETag对应的请求首部If-None-Match或与Last-Modified对应的请求首部If-Modified-Since

  2. 如果是重复的请求:

    • 浏览器判断缓存是否过期(通过Cache-ControlExpires确定)
      • 如果未过期,直接使用缓存内容,也就是强缓存命中,并不会产生新的请求

      • 如果已过期,会发起新的请求,并且请求会带上If-None-MatchIf-Modified-Since,或者兼具两者

      • 服务器收到请求,进行缓存的新鲜度再验证:

        • 首先检查请求是否有If-None-Match首部,没有则继续下一步,有则将其值与文档的最新ETag匹配,失败则认为缓存不新鲜,成功则继续下一步
        • 接着检查请求是否有If-Modified-Since首部,没有则保留上一步验证结果,有则将其值与文档最新修改时间比较验证,失败则认为缓存不新鲜,成功则认为缓存新鲜

        当两个首部皆不存在或者验证结果是不新鲜时,发送200及最新文件,并在首部更新新鲜度。

        当验证结果是缓存仍然新鲜时(也就是弱缓存命中),不需发送文件,仅发送304,并在首部更新新鲜度

为了能启用或关闭某种验证机制,我们在配置文件里增添如下配置项:

default.json

  1. {
  2. ...
  3. "cacheControl": true,
  4. "expires": true,
  5. "etag": true,
  6. "lastModified": true,
  7. "maxAge": 5
  8. }

这里为了能测试到缓存过期,将过期时间设成了非常小的5秒。

StaticServer类中接收这些配置:

  1. class StaticServer {
  2. constructor() {
  3. ...
  4. this.enableCacheControl = config.cacheControl;
  5. this.enableExpires = config.expires;
  6. this.enableETag = config.etag;
  7. this.enableLastModified = config.lastModified;
  8. this.maxAge = config.maxAge;
  9. }

现在,我们要在原来的respondFile前横加一杠,增加是要返回304还是200的逻辑。

  1. respond(pathName, req, res) {
  2. fs.stat(pathName, (err, stat) => {
  3. if (err) return respondError(err, res);
  4. this.setFreshHeaders(stat, res);
  5. if (this.isFresh(req.headers, res._headers)) {
  6. this.responseNotModified(res);
  7. } else {
  8. this.responseFile(pathName, res);
  9. }
  10. });

  11. }

准备返回文件前,根据配置,添加缓存相关的响应首部。

  1. generateETag(stat) {
  2. const mtime = stat.mtime.getTime().toString(16);
  3. const size = stat.size.toString(16);
  4. return `W/"${size}-${mtime}"`;
  5. }

  6. setFreshHeaders(stat, res) {
  7. const lastModified = stat.mtime.toUTCString();
  8. if (this.enableExpires) {
  9. const expireTime = (new Date(Date.now() + this.maxAge * 1000)).toUTCString();
  10. res.setHeader('Expires', expireTime);
  11. }
  12. if (this.enableCacheControl) {
  13. res.setHeader('Cache-Control', `public, max-age=${this.maxAge}`);
  14. }
  15. if (this.enableLastModified) {
  16. res.setHeader('Last-Modified', lastModified);
  17. }
  18. if (this.enableETag) {
  19. res.setHeader('ETag', this.generateETag(stat));
  20. }
  21. }

需要注意的是,上面使用了ETag弱验证器,并不能保证缓存文件与服务器上的文件是完全一样的。关于强验证器如何实现,可以参考etag包的源码。

下面是如何判断缓存是否仍然新鲜:

  1. isFresh(reqHeaders, resHeaders) {
  2. const noneMatch = reqHeaders['if-none-match'];
  3. const lastModified = reqHeaders['if-modified-since'];
  4. if (!(noneMatch || lastModified)) return false;
  5. if(noneMatch && (noneMatch !== resHeaders['etag'])) return false;
  6. if(lastModified && lastModified !== resHeaders['last-modified']) return false;
  7. return true;
  8. }

需要注意的是,http首部字段名是不区分大小写的(但http method应该大写),所以平常在浏览器中会看到大写或小写的首部字段。

使用Node.js搭建静态资源服务器,第6张

使用Node.js搭建静态资源服务器,第7张

但是nodehttp模块将首部字段都转成了小写,这样在代码中使用起来更方便些。所以访问header要用小写,如reqHeaders['if-none-match']。不过,仍然可以用req.rawreq.rawHeaders来访问原headers,它是一个[name1, value1, name2, value2, ...]形式的数组。

现在来测试一下,因为设置的缓存有效时间是极小的5s,所以强缓存几乎不会命中,所以第二次访问文件会发出新的请求,因为服务端文件并没做什么改变,所以会返回304。

使用Node.js搭建静态资源服务器,第8张

现在来修改一下请求的这张图片,比如修改一下size,目的是让服务端的再验证失败,因而必须给客户端发送200和最新的文件。

使用Node.js搭建静态资源服务器,第9张

接下来把缓存有效时间改大一些,比如10分钟,那么在10分钟之内的重复请求,都会命中强缓存,浏览器不会向服务端发起新的请求(但network依然能观察到这条请求)。

使用Node.js搭建静态资源服务器,第10张

内容编码

服务器在发送很大的文档之前,对其进行压缩,可以节省传输用时。其过程是:

  1. 浏览器在访问网站时,默认会携带Accept-Encoding
  2. 服务器在收到请求后,如果发现存在Accept-Encoding请求头,并且支持该文件类型的压缩,压缩响应的实体主体(并不压缩头部),并附上Content-Encoding首部
  3. 浏览器收到响应,如果发现有Content-Encoding首部,按其值指定的格式解压报文

对于图片这类已经经过高度压缩的文件,无需再额外压缩。因此,我们需要配置一个字段,指明需要针对哪些类型的文件进行压缩。

default.json

  1. {
  2. ...
  3. "zipMatch": "^\.(css|js|html)$"
  4. }

static-server.js

  1. constructor() {
  2. ...
  3. this.zipMatch = new RegExp(config.zipMatch);
  4. }

zlib模块来实现流压缩:

  1. compressHandler(readStream, req, res) {
  2. const acceptEncoding = req.headers['accept-encoding'];
  3. if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
  4. return readStream;
  5. } else if (acceptEncoding.match(/\bgzip\b/)) {
  6. res.setHeader('Content-Encoding', 'gzip');
  7. return readStream.pipe(zlib.createGzip());
  8. } else if (acceptEncoding.match(/\bdeflate\b/)) {
  9. res.setHeader('Content-Encoding', 'deflate');
  10. return readStream.pipe(zlib.createDeflate());
  11. }
  12. }

因为配置了图片不需压缩,在浏览器中测试会发现图片请求的响应中没有Content-Encoding头。

范围请求

最后一步,使服务器支持范围请求,允许客户端只请求文档的一部分。其流程是:

  1. 客户端向服务端发起请求
  2. 服务端响应,附上Accept-Ranges头(值表示表示范围的单位,通常是“bytes”),告诉客户端其接受范围请求
  3. 客户端发送新的请求,附上Ranges头,告诉服务端请求的是一个范围
  4. 服务端收到范围请求,分情况响应:
    • 范围有效,服务端返回206 Partial Content,发送指定范围内内容,并在Content-Range头中指定该范围
    • 范围无效,服务端返回416 Requested Range Not Satisfiable,并在Content-Range中指明可接受范围

请求中的Ranges头格式为(这里不考虑多范围请求了):

  1. Ranges: bytes=[start]-[end]

其中 start 和 end 并不是必须同时具有:

  1. 如果 end 省略,服务器应返回从 start 位置开始之后的所有字节
  2. 如果 start 省略,end 值指的就是服务器该返回最后多少个字节
  3. 如果均未省略,则服务器返回 start 和 end 之间的字节

响应中的Content-Range头有两种格式:

  1. 当范围有效返回 206 时:

    1. Content-Range: bytes (start)-(end)/(total)
  2. 当范围无效返回 416 时:

    1. Content-Range: bytes */(total)

添加函数处理范围请求:

  1. rangeHandler(pathName, rangeText, totalSize, res) {
  2. const range = this.getRange(rangeText, totalSize);
  3. if (range.start > totalSize || range.end > totalSize || range.start > range.end) {
  4. res.statusCode = 416;
  5. res.setHeader('Content-Range', `bytes */${totalSize}`);
  6. res.end();
  7. return null;
  8. } else {
  9. res.statusCode = 206;
  10. res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${totalSize}`);
  11. return fs.createReadStream(pathName, { start: range.start, end: range.end });
  12. }
  13. }

Postman来测试一下。在指定的root文件夹下创建一个测试文件:

testfile.js

  1. This is a test sentence.

请求返回前六个字节 ”This “ 返回 206:

使用Node.js搭建静态资源服务器,第11张

请求一个无效范围返回416:

使用Node.js搭建静态资源服务器,第12张

读取命令行参数

至此,已经完成了静态服务器的基本功能。但是每一次需要修改配置,都必须修改default.json文件,非常不方便,如果能接受命令行参数就好了,可以借助 yargs 模块来完成。

  1. var options = require( "yargs" )
  2. .option( "p", { alias: "port", describe: "Port number", type: "number" } )
  3. .option( "r", { alias: "root", describe: "Static resource directory", type: "string" } )
  4. .option( "i", { alias: "index", describe: "Default page", type: "string" } )
  5. .option( "c", { alias: "cachecontrol", default: true, describe: "Use Cache-Control", type: "boolean" } )
  6. .option( "e", { alias: "expires", default: true, describe: "Use Expires", type: "boolean" } )
  7. .option( "t", { alias: "etag", default: true, describe: "Use ETag", type: "boolean" } )
  8. .option( "l", { alias: "lastmodified", default: true, describe: "Use Last-Modified", type: "boolean" } )
  9. .option( "m", { alias: "maxage", describe: "Time a file should be cached for", type: "number" } )
  10. .help()
  11. .alias( "?", "help" )
  12. .argv;

瞅瞅 help 命令会输出啥:

使用Node.js搭建静态资源服务器,第13张

这样就可以在命令行传递端口、默认页等:

  1. node app.js -p 8888 -i main.html

参考

  1. 使用Node.js搭建简易Http服务器
  2. 博文共赏:Node.js静态文件服务器实战
  3. HTTP 206 Partial Content In Node.js

源码

戳我的 GitHub repo: nodejs-static-webserver

博文也同步在 GitHub,欢迎讨论和指正:使用Node.js搭建静态资源服务器

使用Node.js搭建静态资源服务器的更多相关文章

  1. 使用node搭建静态资源服务器

    安装 npm install yumu-static-server -g 使用 shift+鼠标右键  在此处打开Powershell 窗口 server # 会在当前目录下启动一个静态资源服务器,默 ...

  2. 原生node写一个静态资源服务器

    myanywhere 用原生node做一个简易阉割版的anywhere静态资源服务器,以提升对node与http的理解. 相关知识 es6及es7语法 http的相关网络知识 响应头 缓存相关 压缩相 ...

  3. web站点优化之使用tengine搭建静态资源服务器,静态资源合并加载案例剖析

    在一个项目还是单体架构的时候,所有的js,css,image都会在一个web网站上,看起来并没有什么问题,比如下面这样: 但是当web网站流量起来的时候,这个单体架构必须要进行横向扩展,而在原来的架构 ...

  4. Node项目实战-静态资源服务器

    打开github,在github上创建新项目: Repository name: anydoor Descripotion: Tiny NodeJS Static Web server 选择:publ ...

  5. Nginx配置实例-动静分离实例:搭建静态资源服务器

    场景 Nginx入门简介和反向代理.负载均衡.动静分离理解: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/102790862 U ...

  6. 使用Node.js搭建一个本地服务器

    let http = require('http'); //创建一个http let server = http.createServer((request,response)=>{ //创建一 ...

  7. linux使用Nginx搭建静态资源服务器

    最近公司需要做一个宣传片播放  视频有点大 好几百M 就想到使用Nginx来代理静态资源,在过程中出现了一些问题,比如端口没开.访问是403等,没有成功,后面慢慢查找问题,才发现大部分博客资料的都不全 ...

  8. Node.js搭建静态服务器

    let http = require('http'); let url = require('url'); let fs = require('fs'); let path = require('pa ...

  9. Node.js 搭建 https 协议 服务器

    var https = require('https'); //创建服务器 https var fs = require('fs'); //文件系统的模块 const hostname = '127. ...

随机推荐

  1. [1] Entity Framework / Code First

    CodeFirst是EntityFramework的一种技术手段,因为传统编程方式都是先建立数据库,然后根据数据库模型为应用程序建模,再进行开发:CodeFirst从字面上理解就是代码先行,先在程序中 ...

  2. “前”方有坑,绕道而行(一)-- H5+CSS

    1. 关于  数字.英文 不换行问题: 情景:昨天测试 小程序,输入英文,没有换行,且 下方有横向滚动条: 解决:word-break: word-break:break-all; /*只对英文起作用 ...

  3. JVM中class文件探索与解析(一)

    一直想成为一名优秀的架构师的我,转眼已经工作快两年了,对于java内核了解甚少,闲来时间,看看JVM,吧自己的一些研究写下来供大家参考,有不对的地方请指正. 废话不多说,一起来看看JVM中类文件是如何 ...

  4. FutureTask分析(1.8)

    FutureTask简介 FutureTask用于异步计算,也就是支持异步执行并返回结果.FutureTask本身是一个Runable,所以可以交给Thread来运行,在提交给Thread运行后,可以 ...

  5. crontab中引入环境变量(比如需要执行tomcat的关闭启动)

    起因 crontab中的定时任务,执行到关闭tomcat时,报环境变量找不到 解决方案 1.使用 . /etc/profile 引入环境变量 ###推荐, 实测ubuntu12 成功 2.使用 sou ...

  6. Java NIO 核心组件学习笔记

    背景知识 同步.异步.阻塞.非阻塞 首先,这几个概念非常容易搞混淆,但NIO中又有涉及,所以总结一下[1]. 同步:API调用返回时调用者就知道操作的结果如何了(实际读取/写入了多少字节). 异步:相 ...

  7. [leetcode-561-Array Partition I]

    Given an array of 2n integers, your task is to group these integers into n pairs of integer,say (a1, ...

  8. 【Android Developers Training】 34. 添加一个简单的分享行为(Action)

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  9. Jquery-鼠标事件

    鼠标事件是在用户移动鼠标光标或者使用任意鼠标键点击时触发的.(1):click事件:click事件于用户在元素敲击鼠标左键,并在相同元素上松开左键时触发.        $('p').click(fu ...

  10. MySQL(三)--函数与谓词

    前文已有涉及,这里作为总结重新整理一下. 一.函数 1.算术函数 NUMERIC 是大多数 DBMS 都支持的一种数据类型,通过 NUMBERIC ( 全体位数, 小数位数 ) 的形式来指定数值的大小 ...

DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » 使用Node.js搭建静态资源服务器

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情