Git Product home page Git Product logo

fontend_page's People

Contributors

slngle avatar

Stargazers

 avatar

Watchers

 avatar  avatar

fontend_page's Issues

初始化脚手架pangu的开发

最近由于没有啥喜欢的脚手架 所以自己做了一个 ,因为不管用node 还是用react开发,你的项目结构都是会不同的,比方说node express框架就有个express-generator 但是里面的项目结构我就不是很喜欢,他是用模板引擎的,而我只是希望产出接口,所以这就是我为啥想做个初始化脚手架。
他的主要功能是从gitlab上拉取你想要的初始化项目的资源缓存在本地,并且重写你的package.json。
话不多说,进入正题:
这个工具是基于node的,主要运用了node的node-gitlab模块,cli-promtmo块,fs模块等等,主要的流程就是token获取到gitlab对象 通过list-tree和showfile获取需要的文件,通过fs-white-file写入文件,并且本地缓存,好了 就写这么多了

nodejs上传接口书写

用了nodejs做了个图片上传的接口
前端采用formData
node接收采用multer
跨域采用cors跨域

直接上代码

前端formdata代码

upLoad:function(ev) {
//target 为input标签
var target = ev.target;
if(target && target.files && target.files.length>0) {
var fileObj = target.files[0];//获取文件对象
// 接收上传文件的后台地址 接口地址
var FileController = lib.returnHost() + "graduationDesign/api/lostAndFound/upload_file";
// FormData 对象
var form = new FormData();
form.append("file", fileObj);// 文件对象
// XMLHttpRequest 对象
var xhr = new XMLHttpRequest();
xhr.open("post", FileController, true);

        xhr.onreadystatechange = function () {
                     if (xhr.readyState == 4) {
                              if (xhr.status == 200) {
                                   var response = xhr.responseText;
                                   var J_pzImg = document.querySelector("#J_pzImg");
                                   try {
                                  response = JSON.parse(response);
                                   } catch(ex) {
                                 console.log(ex);
                                  }
                                       //response.url为返回的图片url地址
                                  J_pzImg.src = lib.returnHost() + response.url;
                                  queryData.ttimg = response.url;
                                  AppActionsCommon.setToast({
                               title:'失物招领提示',
                               type:'toast',
                               content:'上传成功'
                                 });
                          }
                     }
               };
               xhr.send(form);
          }else {
                //失败提示
              AppActionsCommon.setToast({
            title:'失物招领提示',
            content:'请选择一个图片!'
          });
        }
}

node端的入口文件 app.js代码

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var settings = require('./settings');
var flash = require('connect-flash');
var multer = require('multer');
var lib = require('./lib/index.js');
var app = express();
//cors 跨域
app.all('', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "
");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("X-Powered-By",' 3.2.1')
res.header("Content-Type", "application/json;charset=utf-8");
next();
});

//multer中间件处理请求 并将图片初始名称转换成10为随机数
app.use(multer({
dest: './public/images',
rename: function (fieldname, filename) {
var newname = lib.randomData(10);
return newname;
}
}));

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

routes文件代码

module.exports = function(app) {
app.post('/graduationDesign/api/lostAndFound/upload_file',function (req,res) {
var params = url.parse(req.url,true);
var query = params.query;

  var imgUrl = req.files.file.path;
  if(imgUrl) {
    res.send({
        status:true,
        url:imgUrl
    });        
  }else {
    res.send({
        status:false,
        message:"上传失败"
    });
  }

});

}

结束 代码就不特别详细的解释了

pm2

PM2 是一个带有负载均衡功能的 Node 应用的进程管理器。

当你要把你的独立代码利用全部的服务器上的所有 CPU,并保证进程永远都活着,0 秒的重载, PM2 是完美的。它非常适合 IaaS 结构,但不要把它用于 PaaS 方案(随后将开发 Paas 的解决方案)。

备注:

SaaS、PaaS 和 IaaS 是云服务模式
SaaS 软件即服务,例如 Google 的 Gmail 邮箱服务,面向应用型用户
PaaS 平台即服务,例如 Google 的 GAE,面向开发型用户
IaaS 基础架构即服务,例如亚马逊的 AWS,IaaS 对于不知道新推出的应用程序/网站会有多成功的创业公司来说非常有用
请参考

云服务模式:SaaS、PaaS 和 IaaS,哪一种适合你?
主要特性
内建负载均衡(使用 Node cluster 集群模块)
后台运行
0 秒停机重载,我理解大概意思是维护升级的时候不需要停机.
具有 Ubuntu 和 CentOS 的启动脚本
停止不稳定的进程(避免无限循环)
控制台检测
提供 HTTP API
远程控制和实时的接口 API ( Nodejs 模块,允许和 PM2 进程管理器交互 )
测试过 Nodejs v0.11/v0.10/v0.8 版本,兼容 CoffeeScript,基于 Linux 和 MacOS。

安装
npm install -g pm2
用法
$ npm install pm2 -g # 命令行安装 pm2
$ pm2 start app.js -i 4 # 后台运行pm2,启动4个app.js
# 也可以把'max' 参数传递给 start
# 正确的进程数目依赖于Cpu的核心数目
$ pm2 start app.js --name my-api # 命名进程
$ pm2 list # 显示所有进程状态
$ pm2 monit # 监视所有进程
$ pm2 logs # 显示所有进程日志
$ pm2 stop all # 停止所有进程
$ pm2 restart all # 重启所有进程
$ pm2 reload all # 0 秒停机重载进程 (用于 NETWORKED 进程)
$ pm2 stop 0 # 停止指定的进程
$ pm2 restart 0 # 重启指定的进程
$ pm2 startup # 产生 init 脚本 保持进程活着
$ pm2 web # 运行健壮的 computer API endpoint (http://localhost:9615)
$ pm2 delete 0 # 杀死指定的进程
$ pm2 delete all # 杀死全部进程
运行进程的不同方式
$ pm2 start app.js -i max # 根据有效CPU数目启动最大进程数目
$ pm2 start app.js -i 3 # 启动3个进程
$ pm2 start app.js -x #用fork模式启动 app.js 而不是使用 cluster
$ pm2 start app.js -x -- -a 23 # 用fork模式启动 app.js 并且传递参数 (-a 23)
$ pm2 start app.js --name serverone # 启动一个进程并把它命名为 serverone
$ pm2 stop serverone # 停止 serverone 进程
$ pm2 start app.json # 启动进程, 在 app.json里设置选项
$ pm2 start app.js -i max -- -a 23 #在--之后给 app.js 传递参数
$ pm2 start app.js -i max -e err.log -o out.log # 启动 并 生成一个配置文件
你也可以执行用其他语言编写的app ( fork 模式):
$ pm2 start my-bash-script.sh -x --interpreter bash
$ pm2 start my-python-script.py -x --interpreter python
0 秒停机重载:这项功能允许你重新载入代码而不用失去请求连接。

注意:
仅能用于 web 应用
运行于 Node 0.11.x 版本
运行于 cluster 模式(默认模式)
$ pm2 reload all
CoffeeScript:
$ pm2 start my_app.coffee #这就是全部
PM2 准备好为产品级服务了吗?
只需在你的服务器上测试

$ git clone https://github.com/Unitech/pm2.git
$ cd pm2
$ npm install # 或者 npm install --dev ,如果devDependencies 没有安装
$ npm test
pm2 list:列出由 PM2 管理的所有进程信息,还会显示一个进程会被启动多少次,因为没处理的异常。

pm2 monit:监视每个 node 进程的 CPU 和内存的使用情况。

log4js解释

Log4***,Log4是由Apache提供的多平台下多语言下日志书写扩展包,目的很简单就是使日志书写更加方便简洁,同时对不同的业务日志能够进行灵活的分文件记录,同时也包含着详细的等级配置,为之后分级输出,检索,及程序自动解析提供更加便捷的支持(一家之言,非官方描述,领会精神).Log4有很多语言的实现,比如Log4cpp,Log4j,Log4net等等,看名字就知道对应是支持什么的啦.而我们今天要说的Log4js是Log4***针对的NodeJS语言的实现包,除了基本功能外还扩展了一部分专门针对NodeJS而添加的特性.由于没有深入研究,这里就不详细讨论啦.
OK,进入正题,下面我们主要精力放在Log4js在实战中的详细使用上.
Log4js的快速上手
搭建一个简单工程并安装Log4js包,并建立一个日志存储目录:
mkdir Log4jsTest
cd Log4jsTest
mkdir logs
mkdir logs/log_file
mkdir logs/log_date
npm install log4js

   在工程根目录添加如下两个文件
    log4js.json: log4js的配置文件
    log_start.js: 测试程序的启动文件
        
编写配置文件:log4js.json
{
    "appenders":
        [
            {
                "type":"console",
                "category":"console"
            },
            {
                "category":"log_file",
                "type": "file",
                "filename": "./logs/log_file/file.log",
                "maxLogSize": 104800,
                "backups": 100
            },
            {
                "category":"log_date",
                "type": "dateFile",
                "filename": "./logs/log_date/date",
                "alwaysIncludePattern": true,
                "pattern": "-yyyy-MM-dd-hh.log"

            }
        ],
    "replaceConsole": true,
    "levels":
    {
        "log_file":"ALL",
        "console":"ALL",
        "log_date":"ALL"
    }
}

编写启动文件,并添加测试代码:log_start.js
var log4js = require("log4js");
var log4js_config = require("./log4js.json");
log4js.configure(log4js_config);


console.log("log_start start!");

var LogFile = log4js.getLogger('log_file');

LogFile.trace('This is a Log4js-Test');
LogFile.debug('We Write Logs with log4js');
LogFile.info('You can find logs-files in the log-dir');
LogFile.warn('log-dir is a configuration-item in the log4js.json');
LogFile.error('In This Test log-dir is : \'./logs/log_test/\'');

console.log("log_start end!");
输出效果
    在./logs/log_file/目录下 生成了一个文件 file.log
    里面的内容如下:

[2015-01-24 16:38:32.332] [TRACE] log_file - This is a Log4js-Test
[2015-01-24 16:38:32.333] [DEBUG] log_file - We Write Logs with log4js
[2015-01-24 16:38:32.333] [INFO] log_file - You can find logs-files in the log-dir
[2015-01-24 16:38:32.333] [WARN] log_file - log-dir is a configuration-item in the log4js.json
[2015-01-24 16:38:32.333] [ERROR] log_file - In This Test log-dir is : './logs/log_test/'

总结:
  log4js的使用非常简单:
    1.安包(npm install log4js)
    2.创建日志目录(./logs/log_fie/)
    3.添加一个日志输出规则的配置文件(log4js.json)
     (这个也是有缺省的,但往往缺省配置是不满足使用需求的)
    4.代码中加载log4js,并将配置文件获取到调用一下配置方法(log4js.configure(cfg.json))
    5.写日志log4js.getLogger('log_test').debug("随便写日志啦!!!") 

二.Log4js的配置详解

针对快速上手中的工程,我们将详细分析一下log4js各个属性的作用和使用发放,此处只针对用法上的讨论,实现原理和性能上的深入研究有兴趣的可以直接看一下源码.
1.appenders属性
appenders是配置文件的一级属性:它的作用是配置输出源.后续我们真正输出日志的对象就是log4js的下属的输出源.举个例子说一下这个模式:一个组织要打扫卫生,那么组织本身是一个机构不能打扫卫生,只能由组织内各个员工来做打扫卫生的事.整个log4js可以理解成一个负责日志输出的组织,那么真正的日志输出是依靠的员工们就是appenders数组,appenders内每一个对象就是一个日志输出员工,基于这样的结构,我们自然也不难想出,每个员工都有自己的特性,他们输出的日志规则是不一样的.我继续讨论appenders的子属性.
1.1 category配置
category翻译过来叫做种类.实际上更简单的理解成这个写日志员工的名字.
当我们有多个员工时就依靠与这个字段来区分,前面例子中,写日志前有这样一行code:
log4js.getLogger('log_file').debug(...);
这个getLogger()的参数就是category的配置内容,可以是任意字符串(吐槽:我没有实验中文或者特殊符号是否支持,因为No zuo No die,针对zuo的设计我一向是避开而不是去验证是否可zuo!)
1.2 type配置
type字段是控制日志输出对象的是什么类型的,比较常用的配置有三个:
a."type":"console":
type配置为console表示控制台,在此种配置下,往往用于调试时.细节参见2.replaceConsole中的描述.
b."type":"file":
type配置为file表示日志输出为普通文件,在此种配置下,日志会输出到目标文件夹的目标文件中,并会随着文件大小的变化自动份文件.
该模式下的具体生成文件方法:
相关有效配置包含:maxLogSize,backups,filename
相关无效配置包含:pattern,alwaysIncludePattern
求助:
c."type":"datefile"
type配置为datefile表示是输出按时间分文件的日志,在此种配置下,日志会输出到目标目录下,并以时间格式命名,随着时间的推移,以时间格式命名的文件如果尚未存在,则自动创建新的文件.
该模式下的具体生成文件方法:
相关有效配置包含:pattern,alwaysIncludePattern,filename
相关无效配置包含:maxLogSize,backups
求助:
在datefile模式下,我暂时没有找到同一时间下文件过大后自动分文件的方法.在type为file模式下,我暂时没有找到可以追加时间标签的命名方法.
这个需求很实用,我个人认为log4js应该实现这样的需求,但目前我没发现.如果那位朋友深入了解log4js可以告知我配置方法,或者明确告诉我不支持此种配置,万分感谢!

  1.3 filename配置
    a.filename是一个目录加上文件名,路径就是日志文件存储的路径.
    b.此路径可以是相对路径也可以绝对路径,当是相对路径时,是相对于工程根目录.
    c.无论是相对路径还是绝对路径,路径过程中的所有文件夹必须事先手动创建好,log4js不会自动创建,如路径不存在则会报错.
    d.最后的文件名就是输出文件的名字模版,真实的名字会一定的修改,
      d1:type:datefile 时会加上时间标签,如 [log-2015-01-24 , log-2015-01-25]
      d2:type:file时 如果文件过大,份文件后会增加一个编号标签. [log.1 log.2 log.3 ...]
  1.4 maxLogSize配置
    这个只在type:file模式有效.表示文件多大时才会创建下一个文件,单位是字节.实际设置时具体的值根据业务来定,但是不推荐大于100Mb.
  1.5 pattern配置
    这个只在type:datefile模式有效.表示一个文件的时间命名模式.在生成文件中会依照pattern配置来在filename的文件结尾追加一个时间串来命名文件.上个例子:
    配置文件内容:
            {
                "category":"log_date",
                "type": "dateFile",
                "filename": "./logs/log_date/date",
                "alwaysIncludePattern": true,
                "pattern": "-yyyy-MM-dd-hh:mm:ss.log"
            }
    此时生成的文件名就是date-2015-01-24-14:24:12.log
    pattern精确到ss(秒)就是一秒一个文件,精确到mm(分)就是一分一个文件,一次类推:hh(小时),dd(天),MM(月),yyyy(年),yy(年后两位),注意大小写!
    pattern是有默认配置的,默认配置是".yyyy-MM-dd"
  1.6 alwaysIncludePattern:
    这个只在type:datefile模式有效.
    这个是个开关配置 ture(默认值)是开启pattern,false是不开启pattern,不开启时datefile文件将无任何时间后缀,也不会分文件.
  1.7 backups配置
    这个只在type:file模式有效,表示备份的文件数量,如果文件过多则会将最旧的删除.
    type:file模式下log4js的命名规则:正在写的文件就叫filename中配置的文件名,文件过大后会追加数字 例如 log.1 log.2 log.3 , 直至文件数量达到backups时会把最旧的删除.
    当创建一个新的文件时,log4js会把所有之前的文件的.数字编号都顺延一位,最后将刚刚出现的大文件后面追加.1; 这种模式下应该注意大文件拷贝时对命名的影响,所以maxLogSize不要设置过大.


2.replaceConsole配置
这个配置是表示是否替换控制台输出.当配置文件中配置了appenders中配置了type:console的员工,并且replaceConsole:true时,代码中控制台输出(console.log  console.error)的内容将会以log4js格式输出到控制台中.
再说一个很实用的小技巧:log4js的时时调试输出:
当我们把实际生产环境的log4js.json配置好后,在调试阶段,日志会输出到各个文件中,试试调试起来很不方便,那么我们可以将各个日志输出员工的type配置为console,这样日志信息就会全都汇总到控制台输出.
此时如果再添加一个如下日志员工配置,则代码中nodejs系统提供的console.log也会输出到控制台中.
            {
                "type":"console",
                "category":"console"
            }
其中category的名字必须叫console,否则无效,
replaceConsole:ture时如果不加这行,nodejs系统提供的console.log()输出的内容将不会显示
我把这部分内容的配置重新贴一下:
配置如下:
{
    "appenders":
        [
            {
                "type":"console",
                "category":"console"
            },
            {
                "category":"log_file",
                "type": "console",
                "filename": "./logs/log_file/file.log",
                "maxLogSize": 104800,
                "backups": 100
            },
            {
                "category":"log_date",
                "type": "console",
                "filename": "./logs/log_date/date",
                "alwaysIncludePattern": true,
                "pattern": "-yyyy-MM-dd-hh.log"
            }
        ],
    "replaceConsole": true,
    "levels":
    {
        "log_file":"ALL",
        "console":"ALL",
        "log_date":"ALL"
    }
}

日志输出:
/usr/local/bin/node log_start.js
[2015-01-24 15:20:53.395] [INFO] console - log_start start!
[2015-01-24 15:20:53.401] [TRACE] log_file - This is a Log4js-Test
[2015-01-24 15:20:53.402] [DEBUG] log_file - We Write Logs with log4js
[2015-01-24 15:20:53.402] [INFO] log_file - You can find logs-files in the log-dir
[2015-01-24 15:20:53.402] [WARN] log_file - log-dir is a configuration-item in the log4js.json
[2015-01-24 15:20:53.402] [ERROR] log_file - In This Test log-dir is : './logs/log_test/'
[2015-01-24 15:20:53.402] [INFO] console - log_start end!
Process finished with exit code 0

WebStrom中看这部分日志是有颜色的!很方便!!!

3.levels配置
levels配置也是一个一级属性,它控制着日志的输出级别.在发布的程序,如果很稳定,一些不重要的日志是需要隐去的,但当调试阶段或者环境异常时我们需要重现所有流程,就需要全面的日志.
levels的结构中配置着若干个属性,一般与appenders中的员工对应,其中属性名是appenders中的员工名(也就是category的值),属性值是一个表示等级的字符串.
log4js的levels配置共分为8个等级(也就是日志等级),由低到高分别为:ALL TRACE DEBUG INFO WARN ERROR FATAL OFF.
只有大于等于日志配置级别的信息才能输出出来.
举个例子:我们把刚才的log_file的日志输级别修改为ERROR.
那么最终输出的日志为如下内容:

/usr/local/bin/node log_start.js
[2015-01-24 15:24:27.537] [INFO] console - log_start start!
[2015-01-24 15:24:27.540] [ERROR] log_file - In This Test log-dir is : './logs/log_test/'
[2015-01-24 15:24:27.540] [INFO] console - log_start end!
Process finished with exit code 0

只有ERROR输出出来啦.(ERROR上下的两行不是log_file员工输出来的,是console员工输出出来的,而它的输出级别是ALL,最低级,全部输出)

三.Log4js的常见问题和小技巧

配置文件的格式设定
配置文件其实就是一个js对象,json,js,或者自己通过各种set方法赋值出来一个都一样
最开始说需要将配置文件与配置文件log4js.json与log4js模块关联,也就是调用configure()函数加载配置,其实此时就是需要一个JavaScript对象而已,既然如此,我们完全可以把配置文件写成js格式的文件,类似于这样的:
module.exports = { ... 这里面的内容就是上面贴的json啦};
这种模型的优势是如果配置中有动态信息,可以在配置中添加函数,比如用文件名以pid命名,在配置时可以动态获取pid然后字符串拼接到filename上.另一个优势是json不支持注释,写成js后可以添加注释.
这适用于比较复杂的应用环境中.
小技巧:新日志用法
我们新添加一个功能,新功能出问题的概率高一些,我们希望新功能的日志更加全面,但是又不希望把levels的日志输出级别降低,这样会导致全程序日志量暴增,这个时候我们可以使用一个小技巧,log4js提供好几个级别的日志体系,我们将其中的某一个较高的体系协商好不在正常逻辑中使用,而是留给新功能的日志来使用,由于它的级别是足够高的,所以会有全面的输出,等业务稳定后在将各个日志还原回理应所在的版本就可以啦!我目前就习惯将ERROR级别的log用作新日志getLogger("test").error("..."),

四.附加:真实项目中Log4js的详细配置举例:

应用场景:
  这是一个任务系统,有大量用户连接并获取信息.业务是以客户端发送消息为驱动的,一个消息一组任务,各个任务之间没有关联关系.
配置文件:
  我的真实项目中我设置了6个日志输出员工,其中一个是console类型(console),一个是file类型(log_info),其余4个是datefile类型(log_stat,log_trace,log_error,log_todo).
  console    类型用于捕捉到不小心写成系统日志内容(console.log),
  log_info:  详细日志,主体日志都在这里,使用file类型,100MB一本,根据我的赢盘量我保存100本.
  log_stat:  用于输出一些统计信息,统计信息数量固定,不依赖于用户量变化,且较少,所以设置为           datefile,按天输出看起来也清晰.
  log_trace: 有海量用户并消息驱动处理业务,所以添加trace业务,每个消息记录一条,包含用户名,便于快速定位一个用户的所有操作,考虑到我现在的业务量这个一天一本还是可以接收的,故使用datefile格式
  log_error: 异常信息,数量不会特别多,使用datefile格式
  log_todo:  记录一些需要人工处理业务,日志量不会很多,使用datefile格式


细节如下:
{
    "appenders":
        [
            {
                "category":"console",
                "type":"console"
            },
            {
                "category":"log_info",
                "type": "file",
                "filename": "./logs/log_info/info.log",
                "maxLogSize": 104857500,
                "backups": 100
            },
            {
                "category": "log_stat",
                "type": "datefile",
                "filename": "./logs/log_stat/stat"
            },
            {
                "category": "log_trace",
                "type": "datefile",
                "filename": "./logs/log_trace/trace"
            },
            {
                "category": "log_error",
                "type": "datefile",
                "filename": "./logs/log_error/error"

            },
            {
                "category": "log_todo",
                "type": "datefile",
                "filename": "./logs/log_todo/todo"
            }
        ],
    "replaceConsole": true,
    "levels":
    {
        "log_info":"ALL",
        "log_stat": "ALL",
        "log_trace":"ALL",
        "log_error":"ALL",
        "log_todo":"ALL"
    }
}
实际使用中的其他细节的简略概要   
  配置文件我配置了三套,分别是
  开发调试环境的,所有type都是console
  内网测试环境,如上
  线上环境配置,路径和日志级别有所改动.

http https 等。。。

作为一个经常和web打交道的程序员,了解这些协议是必须的,本文就向大家介绍一下这些协议的区别和基本概念,文中可能不局限于前端知识,还包括一些运维,协议方面的知识,希望能给读者带来一些收获,如有不对之处还请指出。

  1. web始祖HTTP
    全称:超文本传输协议(HyperText Transfer Protocol) 伴随着计算机网络和浏览器的诞生,HTTP1.0也随之而来,处于计算机网络中的应用层,HTTP是建立在TCP协议之上,所以HTTP协议的瓶颈及其优化技巧都是基于TCP协议本身的特性,例如tcp建立连接的3次握手和断开连接的4次挥手以及每次建立连接带来的RTT延迟时间。

  2. HTTP与现代化浏览器
    早在HTTP建立之初,主要就是为了将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器。也是说对于前端来说,我们所写的HTML页面将要放在我们的web服务器上,用户端通过浏览器访问url地址来获取网页的显示内容,但是到了WEB2.0以来,我们的页面变得复杂,不仅仅单纯的是一些简单的文字和图片,同时我们的HTML页面有了CSS,Javascript,来丰富我们的页面展示,当ajax的出现,我们又多了一种向服务器端获取数据的方法,这些其实都是基于HTTP协议的。同样到了移动互联网时代,我们页面可以跑在手机端浏览器里面,但是和PC相比,手机端的网络情况更加复杂,这使得我们开始了不得不对HTTP进行深入理解并不断优化过程中。

  3. HTTP的基本优化
    影响一个HTTP网络请求的因素主要有两个:带宽和延迟。
    带宽:如果说我们还停留在拨号上网的阶段,带宽可能会成为一个比较严重影响请求的问题,但是现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了。
    延迟:
    浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制,后续请求就会被阻塞。
    DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。
    建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。

  4. HTTP1.0和HTTP1.1的一些区别
    HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议。 主要区别主要体现在:
    缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
    带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
    错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
    Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。
    长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。以下是常见的HTTP1.0:

区别用一张图来体现:

  1. HTTP1.0和1.1现存的一些问题
    上面提到过的,HTTP1.x在传输数据时,每次都需要重新建立连接,无疑增加了大量的延迟时间,特别是在移动端更为突出。
    HTTP1.x在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,这在一定程度上无法保证数据的安全性。
    HTTP1.x在使用时,header里携带的内容过大,在一定程度上增加了传输的成本,并且每次请求header基本不怎么变化,尤其在移动端增加用户流量。
    虽然HTTP1.x支持了keep-alive,来弥补多次创建连接产生的延迟,但是keep-alive使用多了同样会给服务端带来大量的性能压力,并且对于单个文件被不断请求的服务(例如图片存放网站),keep-alive可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。

  2. HTTPS应声而出
    为了解决以上问题,网景在1994年创建了HTTPS,并应用在网景导航者浏览器中。 最初,HTTPS是与SSL一起使用的;在SSL逐渐演变到TLS时(其实两个是一个东西,只是名字不同而已),最新的HTTPS也由在2000年五月公布的RFC 2818正式确定下来。简单来说,HTTPS就是安全版的HTTP,并且由于当今时代对安全性要求更高,chrome和firefox都大力支持网站使用HTTPS,苹果也在ios 10系统中强制app使用HTTPS来传输数据,由此可见HTTPS势在必行。

  3. HTTPS与HTTP的一些区别
    HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。
    HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。
    HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
    HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。

  4. HTTPS改造
    如果一个网站要全站由HTTP替换成HTTPS,可能需要关注以下几点:
    安装CA证书,一般的证书都是需要收费的,这边推荐一个比较好的购买证书网站:1)Let's Encrypt,免费,快捷,支持多域名(不是通配符),三条命令即时签署+导出证书。缺点是暂时只有三个月有效期,到期需续签。2Comodo PositiveSSL,收费,但是比较稳定。
    在购买证书之后,在证书提供的网站上配置自己的域名,将证书下载下来之后,配置自己的web服务器,同时进行代码改造。
    HTTPS 降低用户访问速度。SSL握手,HTTPS 对速度会有一定程度的降低,但是只要经过合理优化和部署,HTTPS 对速度的影响完全可以接受。在很多场景下,HTTPS 速度完全不逊于 HTTP,如果使用 SPDY,HTTPS 的速度甚至还要比 HTTP 快。
    相对于HTTPS降低访问速度,其实更需要关心的是服务器端的CPU压力,HTTPS中大量的密钥算法计算,会消耗大量的CPU资源,只有足够的优化,HTTPS 的机器成本才不会明显增加。
    推荐一则淘宝网改造HTTPS的文章。

  5. 使用SPDY加快你的网站速度
    2012年google如一声惊雷提出了SPDY的方案,大家才开始从正面看待和解决老版本HTTP协议本身的问题,SPDY可以说是综合了HTTPS和HTTP两者有点于一体的传输协议,主要解决:
    降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率。
    请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容。
    header压缩。前面提到HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量。
    基于HTTPS的加密协议传输,大大提高了传输数据的可靠性。
    服务端推送(server push),采用了SPDY的网页,例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。SPDY构成图:

SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTTP协议(将HTTP1.x的内容封装成一种新的frame格式),同时可以使用已有的SSL功能。

兼容性:

  1. HTTP2.0的前世今生
    顾名思义有了HTTP1.x,那么HTTP2.0也就顺理成章的出现了。HTTP2.0可以说是SPDY的升级版(其实原本也是基于SPDY设计的),但是,HTTP2.0 跟 SPDY 仍有不同的地方,主要是以下两点:
    HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
    HTTP2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATE

  2. HTTP2.0的新特性
    新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
    多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。多路复用原理图:

header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。目前,有大多数网站已经启用HTTP2.0,例如YouTuBe,淘宝网等网站,利用chrome控制台可以查看是否启用H2:

更多关于HTTP2的问题可以参考:HTTP2奇妙日常,以及HTTP2.0的官方网站。
关于HTTP2和HTTP1.x的区别大致可以看下图:

  1. HTTP2.0的升级改造
    对比HTTPS的升级改造,HTTP2.0或许会稍微简单一些,你可能需要关注以下问题:
    前文说了HTTP2.0其实可以支持非HTTPS的,但是现在主流的浏览器像chrome,firefox表示还是只支持基于 TLS 部署的HTTP2.0协议,所以要想升级成HTTP2.0还是先升级HTTPS为好。
    当你的网站已经升级HTTPS之后,那么升级HTTP2.0就简单很多,如果你使用NGINX,只要在配置文件中启动相应的协议就可以了,可以参考NGINX白皮书,NGINX配置HTTP2.0官方指南。
    使用了HTTP2.0那么,原本的HTTP1.x怎么办,这个问题其实不用担心,HTTP2.0完全兼容HTTP1.x的语义,对于不支持HTTP2.0的浏览器,NGINX会自动向下兼容的。

后记
以上就是关于HTTP,HTTP2.0,SPDY,HTTPS的一些基本理论,有些内容没有深入讲解,大家可以跟进参考连接具体查看。
关于HTTP1.x的一些优化方式,例如文件合并压缩,资源cdn,js,css优化等等同样使用与HTTP2.0和HTTPS,所以web前端的优化,还是要继续进行。
其实WEB发展如此迅速的今天,有些技术是真的要与时俱进的,就像苹果宣布ios 10必须使用HTTPS开始,关于web协议革新就已经开始了,为了更好的性能,更优越的方式,现在就开始升级改造吧

一些概念性的东西扫盲

网关等的解释:
假设你的名字叫小不点(很小),你住在一个大院子里,你的邻居有很多小伙伴,父母是你的网关。当你想跟院子里的某个小伙伴玩,只要你在院子里大喊一声他的名字,他听到了就会回应你,并且跑出来跟你玩。
但是你家长不允许你走出大门,你想与外界发生的一切联系,都必须由父母(网关)用电话帮助你联系。假如你想找你的同学小明聊天,小明家住在很远的另外一个院子里,他家里也有父母(小明的网关)。但是你不知道小明家的电话号码,不过你的班主任老师有一份你们班全体同学的名单和电话号码对照表,你的老师就是你的DNS服务器。于是你在家里和父母有了下面的对话:
小不点:妈妈(或爸爸),我想找班主任查一下小明的电话号码行吗?家长:好,你等着。(接着你家长给你的班主任挂了一个电话,问清楚了小明的电话)问到了,他家的号码是211.99.99.99
小不点:太好了!妈(或爸),我想找小明,你再帮我联系一下小明吧。
家长:没问题。(接着家长向电话局发出了请求接通小明家电话的请求,最后一关当然是被转接到了小明家家长那里,然后他家长把电话给转到小明).
就这样你和小明取得了联系。
如果搞清了什么是网关,默认网关也就好理解了。就好像一个房间可以有多扇门一样,一台主机可以有多个网关。默认网关的意思是一台主机如果找不到可用的网关,就把数据包发给默认指定的网关,由这个网关来处理数据包。默认网关。默认网关一般填写192.168.x.1

系统资源:
当应用程序在Windows中运行时,Windows必须实时"跟踪"该应用程序的运行,并保留与之相关的许多信息,如光标、窗口的状况等,这些信息由Windows保留在一种叫堆的内存块中,堆的英文为Heap。简单地说,堆是采用特殊机制管理的内存块。由Windows的一个系统内核User.exe管理的堆叫作User资源堆(User Resource Heap),由另一个系统内核Gdi.exe管理的堆叫作GDI资源堆(Graphical Device Interface Resource Heap),User资源堆和GDI资源堆合称为系统资源堆(System Resource Heap),习惯上就把它们叫作系统资源(System Resource)。
不要将系统资源和CPU资源(CPU使用率)相混淆,硬盘、光驱、软猫的数据处理、显卡的3D图像处理、声卡的3D音效处理占用的都是CPU时间(即消耗CPU资源),而不是系统资源,这些硬件设备的先进与否与占用系统资源的多少根本没有任何关系,可至今许多人还是将它们混为一谈。按习惯,谈到硬件的资源占用一般是指其CPU资源的占用,而软件的资源占用既包括CPU资源占用又包括系统资源(堆)占用,但计算机用户关心的一般是后者,因此谈到软件的资源占用时一般是指其对系统资源的占用。

CPU使用率
CPU使用率其实就是你运行的程序占用的CPU资源,表示你的机器在某个时间点的运行程序的情况。使用率越高,说明你的机器在这个时间上运行了很多程序,反之较少。使用率的高低与你的CPU强弱有直接关系。现代分时多任务操作系统对 CPU 都是分时间片使用的:比如A进程占用10ms,然后B进程占用30ms,然后空闲60ms,再又是A进程占10ms,B进程占30ms,空闲60ms;如果在一段时间内都是如此,那么这段时间内的占用率为40%。CPU对线程的响应并不是连续的,通常会在一段时间后自动中断线程。未响应的线程增加,就会不断加大CPU的占用。cpu使用率高的原因有很多,但是一般都是由于病毒木马或开机启动项过多所致。高CPU使用率也可能表明应用程序的调整或设计不良。优化应用程序可以降低CPU的使用率。

进程与线程的区别
从一定意义上讲,进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行。
进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。
进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。
线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定。线程的运行中需要使用计算机的内存资源和CPU。
线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定。线程的运行中需要使用计算机的内存资源和CPU。
线程与进程的区别归纳:
a.地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
b.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
c.调度和切换:线程上下文切换比进程上下文切换要快得多。
d.在多线程OS中,进程不是一个可执行的实体。

 osi七层协议各层比喻
   7 应用层:老板
   6 表示层:相当于公司中演示文稿、替老板写信的助理
   5 会话层:相当于公司中收寄信、写信封与拆信封的秘书
   4 传输层:相当于公司中跑邮局的送信职员
   3 网络层:相当于邮局中的排序工人
   2 数据链路层:相当于邮局中的装拆箱工人
   1 物理层:相当于邮局中的搬运工人

node.js之fs模块的使用

nodejs对文件的读写还是相当灵活的,可以根据不同的场景来选择不同的方法。

一.直接操作文件
最简单的两个fs.readFile和fs.writeFile
  举例:这个程序的功能是将一个比较大json格式的文件转换成你想自己要格式的文件。
var fs = require('fs');
fs.readFile('./json.json',function(err,data){
if(err) throw err;

var jsonObj = JSON.parse(data);
var space = ' ';
var newLine = '\n';
var chunks = [];
var length = 0;

for(var i=0,size=jsonObj.length;i<size;i++){
var one = jsonObj[i];
//what value you want
var value1 = one['value1'];
var value2 = one['value2'];
....
var value = value1 +space+value2+space+.....+newLine;
var buffer = new Buffer(value);
chunks.push(buffer);
length += buffer.length;
}

var resultBuffer = new Buffer(length);
for(var i=0,size=chunks.length,pos=0;i<size;i++){
chunks[i].copy(resultBuffer,pos);
pos += chunks[i].length;
}

fs.writeFile('./resut.text',resultBuffer,function(err){
if(err) throw err;
console.log('has finished');
});

});

它的原理是将文件数据一次性全部读入内存,优点就是接下来都是在内存的操作,速度会很快。但缺点也很明显,就是当文件非常大时,会造成内存溢出。
二. 使用文件流
   2.1 读取文件,api相见:fs.createReadSream 和 fs.createWriterStream
以下代码实现的功能就是通过文件流来实现图片的复制:
var fs = require('fs');
var rOption = {
flags : 'r',
encoding : null,
mode : 0666
}

var wOption = {
flags: 'a',
encoding: null,
mode: 0666
}

var fileReadStream = fs.createReadStream('./myjpg.jpg',rOption);
var fileWriteStream = fs.createWriteStream('./new_myjpg.jpg',wOption);

fileReadStream.on('data',function(data){
fileWriteStream.write(data);

});

fileReadStream.on('end',function(){
console.log('readStream end');
fileWriteStream.end();
});

这里再补充在流中非常有用的一个函数:pipe,它以用来把当前的可读流和另外一个可写流连接起来。可读流中的数据会被自动写入到可写流中。使用起来非常方便,依然实现上例中的功能:
var fs = require('fs');

var fileReadStream = fs.createReadStream('./myjpg.jpg');
var fileWriteStream = fs.createWriteStream('./new_myjpg.jpg');
fileReadStream.pipe(fileWriteStream);

fileWriteStream.on('close',function(){
console.log('copy over');
});

用这个函数可以轻松地实现一个静态资源服务器:
var http = require("http");
var fs = require("fs"),
var path = require("path"),
var url = require("url");

var server = http.createServer(function(req, res) {
var pathname = url.parse(req.url).pathname;
console.log(pathname);
var filepath = path.join("./tmp", "wwwroot", pathname);
console.log(filepath);
var stream = fs.createReadStream(filepath, {flags : "r", encoding : null});
stream.on("error", function() {
res.writeHead(404);
res.end();
});
stream.pipe(res);
});
server.on("error", function(error) {
console.log(error);
});
server.listen(8088,function(){
console.log('server listen on 8088');
});

http协议详解

HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。
HTTP协议的主要特点可概括如下:
1.支持客户/服务器模式。
2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

一、HTTP协议详解之URL篇

http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式,HTTP1.1版本中给出一种持续连接的机制,绝大多数的Web开发,都是构建在HTTP协议之上的Web应用。

HTTP URL (URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息)的格式如下:
http://host[":"port][abs_path]
http表示要通过HTTP协议来定位网络资源;host表示合法的Internet主机域名或者IP地址;port指定一个端口号,为空则使用缺省端口80;abs_path指定请求资源的URI;如果URL中没有给出abs_path,那么当它作为请求URI时,必须以“/”的形式给出,通常这个工作浏览器自动帮我们完成。
eg:
1、输入:www.guet.edu.cn
浏览器自动转换成:http://www.guet.edu.cn/
2、http:192.168.0.116:8080/index.jsp

二、HTTP协议详解之请求篇

http请求由三部分组成,分别是:请求行、消息报头、请求正文

1、请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式如下:Method Request-URI HTTP-Version CRLF
其中 Method表示请求方法;Request-URI是一个统一资源标识符;HTTP-Version表示请求的HTTP协议版本;CRLF表示回车和换行(除了作为结尾的CRLF外,不允许出现单独的CR或LF字符)。

请求方法(所有方法全为大写)有多种,各个方法的解释如下:
GET 请求获取Request-URI所标识的资源
POST 在Request-URI所标识的资源后附加新的数据
HEAD 请求获取由Request-URI所标识的资源的响应消息报头
PUT 请求服务器存储一个资源,并用Request-URI作为其标识
DELETE 请求服务器删除Request-URI所标识的资源
TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
CONNECT 保留将来使用
OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求
应用举例:
GET方法:在浏览器的地址栏中输入网址的方式访问网页时,浏览器采用GET方法向服务器获取资源,eg:GET /form.html HTTP/1.1 (CRLF)

POST方法要求被请求服务器接受附在请求后面的数据,常用于提交表单。
eg:POST /reg.jsp HTTP/ (CRLF)
Accept:image/gif,image/x-xbit,... (CRLF)
...
HOST:www.guet.edu.cn (CRLF)
Content-Length:22 (CRLF)
Connection:Keep-Alive (CRLF)
Cache-Control:no-cache (CRLF)
(CRLF) //该CRLF表示消息报头已经结束,在此之前为消息报头
user=jeffrey&pwd=1234 //此行以下为提交的数据

HEAD方法与GET方法几乎是一样的,对于HEAD请求的回应部分来说,它的HTTP头部中包含的信息与通过GET请求所得到的信息是相同的。利用这个方法,不必传输整个资源内容,就可以得到Request-URI所标识的资源的信息。该方法常用于测试超链接的有效性,是否可以访问,以及最近是否更新。
2、请求报头后述
3、请求正文(略)

三、HTTP协议详解之响应篇

在接收和解释请求消息后,服务器返回一个HTTP响应消息。

HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文
1、状态行格式如下:
HTTP-Version Status-Code Reason-Phrase CRLF
其中,HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求
常见状态代码、状态描述、说明:
200 OK //客户端请求成功
400 Bad Request //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden //服务器收到请求,但是拒绝提供服务
404 Not Found //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
eg:HTTP/1.1 200 OK (CRLF)

2、响应报头后述

3、响应正文就是服务器返回的资源的内容

四、HTTP协议详解之消息报头篇

HTTP消息由客户端到服务器的请求和服务器到客户端的响应组成。请求消息和响应消息都是由开始行(对于请求消息,开始行就是请求行,对于响应消息,开始行就是状态行),消息报头(可选),空行(只有CRLF的行),消息正文(可选)组成。

HTTP消息报头包括普通报头、请求报头、响应报头、实体报头。
每一个报头域都是由名字+“:”+空格+值 组成,消息报头域的名字是大小写无关的。

1、普通报头
在普通报头中,有少数报头域用于所有的请求和响应消息,但并不用于被传输的实体,只用于传输的消息。
eg:
Cache-Control 用于指定缓存指令,缓存指令是单向的(响应中出现的缓存指令在请求中未必会出现),且是独立的(一个消息的缓存指令不会影响另一个消息处理的缓存机制),HTTP1.0使用的类似的报头域为Pragma。
请求时的缓存指令包括:no-cache(用于指示请求或响应消息不能缓存)、no-store、max-age、max-stale、min-fresh、only-if-cached;
响应时的缓存指令包括:public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age、s-maxage.
eg:为了指示IE浏览器(客户端)不要缓存页面,服务器端的JSP程序可以编写如下:response.sehHeader("Cache-Control","no-cache");
//response.setHeader("Pragma","no-cache");作用相当于上述代码,通常两者//合用
这句代码将在发送的响应消息中设置普通报头域:Cache-Control:no-cache

Date普通报头域表示消息产生的日期和时间

Connection普通报头域允许发送指定连接的选项。例如指定连接是连续,或者指定“close”选项,通知服务器,在响应完成后,关闭连接

2、请求报头
请求报头允许客户端向服务器端传递请求的附加信息以及客户端自身的信息。
常用的请求报头
Accept
Accept请求报头域用于指定客户端接受哪些类型的信息。eg:Accept:image/gif,表明客户端希望接受GIF图象格式的资源;Accept:text/html,表明客户端希望接受html文本。
Accept-Charset
Accept-Charset请求报头域用于指定客户端接受的字符集。eg:Accept-Charset:iso-8859-1,gb2312.如果在请求消息中没有设置这个域,缺省是任何字符集都可以接受。
Accept-Encoding
Accept-Encoding请求报头域类似于Accept,但是它是用于指定可接受的内容编码。eg:Accept-Encoding:gzip.deflate.如果请求消息中没有设置这个域服务器假定客户端对各种内容编码都可以接受。
Accept-Language
Accept-Language请求报头域类似于Accept,但是它是用于指定一种自然语言。eg:Accept-Language:zh-cn.如果请求消息中没有设置这个报头域,服务器假定客户端对各种语言都可以接受。
Authorization
Authorization请求报头域主要用于证明客户端有权查看某个资源。当浏览器访问一个页面时,如果收到服务器的响应代码为401(未授权),可以发送一个包含Authorization请求报头域的请求,要求服务器对其进行验证。
Host(发送请求时,该报头域是必需的)
Host请求报头域主要用于指定被请求资源的Internet主机和端口号,它通常从HTTP URL中提取出来的,eg:
我们在浏览器中输入:http://www.guet.edu.cn/index.html
浏览器发送的请求消息中,就会包含Host请求报头域,如下:
Host:www.guet.edu.cn
此处使用缺省端口号80,若指定了端口号,则变成:Host:www.guet.edu.cn:指定端口号
User-Agent
我们上网登陆论坛的时候,往往会看到一些欢迎信息,其中列出了你的操作系统的名称和版本,你所使用的浏览器的名称和版本,这往往让很多人感到很神奇,实际上,服务器应用程序就是从User-Agent这个请求报头域中获取到这些信息。User-Agent请求报头域允许客户端将它的操作系统、浏览器和其它属性告诉服务器。不过,这个报头域不是必需的,如果我们自己编写一个浏览器,不使用User-Agent请求报头域,那么服务器端就无法得知我们的信息了。
请求报头举例:
GET /form.html HTTP/1.1 (CRLF)
Accept:image/gif,image/x-xbitmap,image/jpeg,application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,/ (CRLF)
Accept-Language:zh-cn (CRLF)
Accept-Encoding:gzip,deflate (CRLF)
If-Modified-Since:Wed,05 Jan 2007 11:21:25 GMT (CRLF)
If-None-Match:W/"80b1a4c018f3c41:8317" (CRLF)
User-Agent:Mozilla/4.0(compatible;MSIE6.0;Windows NT 5.0) (CRLF)
Host:www.guet.edu.cn (CRLF)
Connection:Keep-Alive (CRLF)
(CRLF)

3、响应报头
响应报头允许服务器传递不能放在状态行中的附加响应信息,以及关于服务器的信息和对Request-URI所标识的资源进行下一步访问的信息。
常用的响应报头
Location
Location响应报头域用于重定向接受者到一个新的位置。Location响应报头域常用在更换域名的时候。
Server
Server响应报头域包含了服务器用来处理请求的软件信息。与User-Agent请求报头域是相对应的。下面是
Server响应报头域的一个例子:
Server:Apache-Coyote/1.1
WWW-Authenticate
WWW-Authenticate响应报头域必须被包含在401(未授权的)响应消息中,客户端收到401响应消息时候,并发送Authorization报头域请求服务器对其进行验证时,服务端响应报头就包含该报头域。
eg:WWW-Authenticate:Basic realm="Basic Auth Test!" //可以看出服务器对请求资源采用的是基本验证机制。

4、实体报头
请求和响应消息都可以传送一个实体。一个实体由实体报头域和实体正文组成,但并不是说实体报头域和实体正文要在一起发送,可以只发送实体报头域。实体报头定义了关于实体正文(eg:有无实体正文)和请求所标识的资源的元信息。
常用的实体报头
Content-Encoding
Content-Encoding实体报头域被用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容的编码,因而要获得Content-Type报头域中所引用的媒体类型,必须采用相应的解码机制。Content-Encoding这样用于记录文档的压缩方法,eg:Content-Encoding:gzip
Content-Language
Content-Language实体报头域描述了资源所用的自然语言。没有设置该域则认为实体内容将提供给所有的语言阅读
者。eg:Content-Language:da
Content-Length
Content-Length实体报头域用于指明实体正文的长度,以字节方式存储的十进制数字来表示。
Content-Type
Content-Type实体报头域用语指明发送给接收者的实体正文的媒体类型。eg:
Content-Type:text/html;charset=ISO-8859-1
Content-Type:text/html;charset=GB2312
Last-Modified
Last-Modified实体报头域用于指示资源的最后修改日期和时间。
Expires
Expires实体报头域给出响应过期的日期和时间。为了让代理服务器或浏览器在一段时间以后更新缓存中(再次访问曾访问过的页面时,直接从缓存中加载,缩短响应时间和降低服务器负载)的页面,我们可以使用Expires实体报头域指定页面过期的时间。eg:Expires:Thu,15 Sep 2006 16:23:12 GMT
HTTP1.1的客户端和缓存必须将其他非法的日期格式(包括0)看作已经过期。eg:为了让浏览器不要缓存页面,我们也可以利用Expires实体报头域,设置为0,jsp中程序如下:response.setDateHeader("Expires","0");

五、利用telnet观察http协议的通讯过程

实验目的及原理:
利用MS的telnet工具,通过手动输入http请求信息的方式,向服务器发出请求,服务器接收、解释和接受请求后,会返回一个响应,该响应会在telnet窗口上显示出来,从而从感性上加深对http协议的通讯过程的认识。

实验步骤:

1、打开telnet
1.1 打开telnet
运行-->cmd-->telnet

1.2 打开telnet回显功能
set localecho

2、连接服务器并发送请求
2.1 open www.guet.edu.cn 80 //注意端口号不能省略

HEAD /index.asp HTTP/1.0
Host:www.guet.edu.cn

/我们可以变换请求方法,请求桂林电子主页内容,输入消息如下/
open www.guet.edu.cn 80

GET /index.asp HTTP/1.0  //请求资源的内容
Host:www.guet.edu.cn  

2.2 open www.sina.com.cn 80 //在命令提示符号下直接输入telnet www.sina.com.cn 80
HEAD /index.asp HTTP/1.0
Host:www.sina.com.cn

3 实验结果:

3.1 请求信息2.1得到的响应是:

HTTP/1.1 200 OK //请求成功
Server: Microsoft-IIS/5.0 //web服务器
Date: Thu,08 Mar 200707:17:51 GMT
Connection: Keep-Alive
Content-Length: 23330
Content-Type: text/html
Expries: Thu,08 Mar 2007 07:16:51 GMT
Set-Cookie: ASPSESSIONIDQAQBQQQB=BEJCDGKADEDJKLKKAJEOIMMH; path=/
Cache-control: private

//资源内容省略

3.2 请求信息2.2得到的响应是:

HTTP/1.0 404 Not Found //请求失败
Date: Thu, 08 Mar 2007 07:50:50 GMT
Server: Apache/2.0.54
Last-Modified: Thu, 30 Nov 2006 11:35:41 GMT
ETag: "6277a-415-e7c76980"
Accept-Ranges: bytes
X-Powered-By: mod_xlayout_jh/0.0.1vhs.markII.remix
Vary: Accept-Encoding
Content-Type: text/html
X-Cache: MISS from zjm152-78.sina.com.cn
Via: 1.0 zjm152-78.sina.com.cn:80<squid/2.6.STABLES-20061207>
X-Cache: MISS from th-143.sina.com.cn
Connection: close

失去了跟主机的连接

按任意键继续...

4 .注意事项:1、出现输入错误,则请求不会成功。
2、报头域不分大小写。
3、更深一步了解HTTP协议,可以查看RFC2616,在http://www.letf.org/rfc上找到该文件。
4、开发后台程序必须掌握http协议

六、HTTP协议相关技术补充

1、基础:
高层协议有:文件传输协议FTP、电子邮件传输协议SMTP、域名系统服务DNS、网络新闻传输协议NNTP和HTTP协议等

中介由三种:代理(Proxy)、网关(Gateway)和通道(Tunnel),一个代理根据URI的绝对格式来接受请求,重写全部或部分消息,通过 URI的标识把已格式化过的请求发送到服务器。网关是一个接收代理,作为一些其它服务器的上层,并且如果必须的话,可以把请求翻译给下层的服务器协议。一 个通道作为不改变消息的两个连接之间的中继点。当通讯需要通过一个中介(例如:防火墙等)或者是中介不能识别消息的内容时,通道经常被使用。
代理(Proxy):一个中间程序,它可以充当一个服务器,也可以充当一个客户机,为其它客户机建立请求。请求是通过可能的翻译在内部或经过传递到其它的 服务器中。一个代理在发送请求信息之前,必须解释并且如果可能重写它。代理经常作为通过防火墙的客户机端的门户,代理还可以作为一个帮助应用来通过协议处 理没有被用户代理完成的请求。
网关(Gateway):一个作为其它服务器中间媒介的服务器。与代理不同的是,网关接受请求就好象对被请求的资源来说它就是源服务器;发出请求的客户机并没有意识到它在同网关打交道。
网关经常作为通过防火墙的服务器端的门户,网关还可以作为一个协议翻译器以便存取那些存储在非HTTP系统中的资源。
通道(Tunnel):是作为两个连接中继的中介程序。一旦激活,通道便被认为不属于HTTP通讯,尽管通道可能是被一个HTTP请求初始化的。当被中继 的连接两端关闭时,通道便消失。当一个门户(Portal)必须存在或中介(Intermediary)不能解释中继的通讯时通道被经常使用。

2、协议分析的优势—HTTP分析器检测网络攻击
以模块化的方式对高层协议进行分析处理,将是未来入侵检测的方向。
HTTP及其代理的常用端口80、3128和8080在network部分用port标签进行了规定

3、HTTP协议Content Lenth限制漏洞导致拒绝服务攻击
使用POST方法时,可以设置ContentLenth来定义需要传送的数据长度,例如ContentLenth:999999999,在传送完成前,内 存不会释放,攻击者可以利用这个缺陷,连续向WEB服务器发送垃圾数据直至WEB服务器内存耗尽。这种攻击方法基本不会留下痕迹。
http://www.cnpaf.net/Class/HTTP/0532918532667330.html

4、利用HTTP协议的特性进行拒绝服务攻击的一些构思
服务器端忙于处理攻击者伪造的TCP连接请求而无暇理睬客户的正常请求(毕竟客户端的正常请求比率非常之小),此时从正常客户的角度看来,服务器失去响应,这种情况我们称作:服务器端受到了SYNFlood攻击(SYN洪水攻击)。
而Smurf、TearDrop等是利用ICMP报文来Flood和IP碎片攻击的。本文用“正常连接”的方法来产生拒绝服务攻击。
19端口在早期已经有人用来做Chargen攻击了,即Chargen_Denial_of_Service,但是!他们用的方法是在两台Chargen 服务器之间产生UDP连接,让服务器处理过多信息而DOWN掉,那么,干掉一台WEB服务器的条件就必须有2个:1.有Chargen服务2.有HTTP 服务
方法:攻击者伪造源IP给N台Chargen发送连接请求(Connect),Chargen接收到连接后就会返回每秒72字节的字符流(实际上根据网络实际情况,这个速度更快)给服务器。

5、Http指纹识别技术
Http指纹识别的原理大致上也是相同的:记录不同服务器对Http协议执行中的微小差别进行识别.Http指纹识别比TCP/IP堆栈指纹识别复杂许 多,理由是定制Http服务器的配置文件、增加插件或组件使得更改Http的响应信息变的很容易,这样使得识别变的困难;然而定制TCP/IP堆栈的行为 需要对核心层进行修改,所以就容易识别.
要让服务器返回不同的Banner信息的设置是很简单的,象Apache这样的开放源代码的Http服务器,用户可以在源代码里修改Banner信息,然 后重起Http服务就生效了;对于没有公开源代码的Http服务器比如微软的IIS或者是Netscape,可以在存放Banner信息的Dll文件中修 改,相关的文章有讨论的,这里不再赘述,当然这样的修改的效果还是不错的.另外一种模糊Banner信息的方法是使用插件。
常用测试请求:
1:HEAD/Http/1.0发送基本的Http请求
2:DELETE/Http/1.0发送那些不被允许的请求,比如Delete请求
3:GET/Http/3.0发送一个非法版本的Http协议请求
4:GET/JUNK/1.0发送一个不正确规格的Http协议请求
Http指纹识别工具Httprint,它通过运用统计学原理,组合模糊的逻辑学技术,能很有效的确定Http服务器的类型.它可以被用来收集和分析不同Http服务器产生的签名。

6、其他:为了提高用户使用浏览器时的性能,现代浏览器还支持并发的访问方式,浏览一个网页时同时建立多个连接,以迅速获得一个网页上的多个图标,这样能更快速完成整个网页的传输。
HTTP1.1中提供了这种持续连接的方式,而下一代HTTP协议:HTTP-NG更增加了有关会话控制、丰富的内容协商等方式的支持,来提供
更高效率的连接。

mongodb从下载到开启

mac 下用 brew 安装mongodb
brew install mongodb
安装的目录就是 /usr/local/Cellar/mongodb/2.4.9
将mongodb手动添加的环境变量中.
export PATH=/usr/local/Cellar/mongodb/2.4.9/bin:${PATH}}
source ~/.bash_profile

第一次启动服务端,这里需要做一些准备工作.
1.默认mongodb 数据文件是放到根目录 data/db 文件夹下,如果没有这个文件,请自行创建.
sudo mkdir -p /data/db
2.修改mongodb配置文件,配置文件默认在 /usr/local/etc 下的 mongod.conf

# Store data in /usr/local/var/mongodb instead of the default /data/db
dbpath = /data/db

# Append logs to /usr/local/var/log/mongodb/mongo.log
logpath = /usr/local/var/log/mongodb/mongo.log
logappend = true

# Only accept local connections
bind_ip = 127.0.0.1

第二行修改成数据库文件写入目录地址,如果准备连接非本地环境的mongodb数据库时,bind_ip = 0.0.0.0 即可.

3.给 /data/db 文件夹赋权限
sudo chown id -u /data/db

4.尝试启动服务器端
运行mongod 看提示 一般会成功

之后运行mongo 今日交互模式 自己玩

MongoDB基本命令
成功启动MongoDB后,再打开一个命令行窗口输入mongo,就可以进行数据库的一些操作。
输入help可以看到基本操作命令:

show dbs:显示数据库列表
show collections:显示当前数据库中的集合(类似关系数据库中的表)
show users:显示用户

use :切换当前数据库,这和MS-SQL里面的意思一样
db.help():显示数据库操作命令,里面有很多的命令
db.foo.help():显示集合操作命令,同样有很多的命令,foo指的是当前数据库下,一个叫foo的集合,并非真正意义上的命令
db.foo.find():对于当前数据库中的foo集合进行数据查找(由于没有条件,会列出所有数据)
db.foo.find( { a : 1 } ):对于当前数据库中的foo集合进行查找,条件是数据中有一个属性叫a,且a的值为1

MongoDB没有创建数据库的命令,但有类似的命令。

如:如果你想创建一个“myTest”的数据库,先运行use myTest命令,之后就做一些操作(如:db.createCollection('user')),这样就可以创建一个名叫“myTest”的数据库。

数据库常用命令

1、Help查看命令提示
help
db.help();
db.yourColl.help();
db.youColl.find().help();
rs.help();

2、切换/创建数据库
use yourDB; 当创建一个集合(table)的时候会自动创建当前数据库

3、查询所有数据库
show dbs;

4、删除当前使用数据库
db.dropDatabase();

5、从指定主机上克隆数据库
db.cloneDatabase(“127.0.0.1”); 将指定机器上的数据库的数据克隆到当前数据库

6、从指定的机器上复制指定数据库数据到某个数据库
db.copyDatabase("mydb", "temp", "127.0.0.1");将本机的mydb的数据复制到temp数据库中

7、修复当前数据库
db.repairDatabase();

8、查看当前使用的数据库
db.getName();
db; db和getName方法是一样的效果,都可以查询当前使用的数据库

9、显示当前db状态
db.stats();

10、当前db版本
db.version();

11、查看当前db的链接机器地址
db.getMongo();
Collection聚集集合

1、创建一个聚集集合(table)
db.createCollection(“collName”, {size: 20, capped: 5, max: 100});

2、得到指定名称的聚集集合(table)
db.getCollection("account");

3、得到当前db的所有聚集集合
db.getCollectionNames();

4、显示当前db所有聚集索引的状态
db.printCollectionStats();

用户相关
1、添加一个用户
db.addUser("name");
db.addUser("userName", "pwd123", true); 添加用户、设置密码、是否只读

2、数据库认证、安全模式
db.auth("userName", "123123");

3、显示当前所有用户
show users;

4、删除用户
db.removeUser("userName");

其他
1、查询之前的错误信息
db.getPrevError();
2、清除错误记录
db.resetError();

查看聚集集合基本信息
1、查看帮助 db.yourColl.help();
2、查询当前集合的数据条数 db.yourColl.count();
3、查看数据空间大小 db.userInfo.dataSize();
4、得到当前聚集集合所在的db db.userInfo.getDB();
5、得到当前聚集的状态 db.userInfo.stats();
6、得到聚集集合总大小 db.userInfo.totalSize();
7、聚集集合储存空间大小 db.userInfo.storageSize();
8、Shard版本信息 db.userInfo.getShardVersion()
9、聚集集合重命名 db.userInfo.renameCollection("users"); 将userInfo重命名为users
10、删除当前聚集集合 db.userInfo.drop();
聚集集合查询

1、查询所有记录
db.userInfo.find();
相当于:select* from userInfo;
默认每页显示20条记录,当显示不下的情况下,可以用it迭代命令查询下一页数据。注意:键入it命令不能带“;”
但是你可以设置每页显示数据的大小,用DBQuery.shellBatchSize= 50;这样每页就显示50条记录了。

2、查询去掉后的当前聚集集合中的某列的重复数据
db.userInfo.distinct("name");
会过滤掉name中的相同数据
相当于:select distict name from userInfo;

3、查询age = 22的记录
db.userInfo.find({"age": 22});
相当于: select * from userInfo where age = 22;

4、查询age > 22的记录
db.userInfo.find({age: {$gt: 22}});
相当于:select * from userInfo where age >22;

5、查询age < 22的记录
db.userInfo.find({age: {$lt: 22}});
相当于:select * from userInfo where age <22;

6、查询age >= 25的记录
db.userInfo.find({age: {$gte: 25}});
相当于:select * from userInfo where age >= 25;

7、查询age <= 25的记录
db.userInfo.find({age: {$lte: 25}});

8、查询age >= 23 并且 age <= 26
db.userInfo.find({age: {$gte: 23, $lte: 26}});

9、查询name中包含 mongo的数据
db.userInfo.find({name: /mongo/});
//相当于%%
select * from userInfo where name like ‘%mongo%’;

10、查询name中以mongo开头的
db.userInfo.find({name: /^mongo/});
select * from userInfo where name like ‘mongo%’;

11、查询指定列name、age数据
db.userInfo.find({}, {name: 1, age: 1});
相当于:select name, age from userInfo;
当然name也可以用true或false,当用ture的情况下河name:1效果一样,如果用false就是排除name,显示name以外的列信息。

12、查询指定列name、age数据, age > 25
db.userInfo.find({age: {$gt: 25}}, {name: 1, age: 1});
相当于:select name, age from userInfo where age >25;

13、按照年龄排序
升序:db.userInfo.find().sort({age: 1});
降序:db.userInfo.find().sort({age: -1});

14、查询name = zhangsan, age = 22的数据
db.userInfo.find({name: 'zhangsan', age: 22});
相当于:select * from userInfo where name = ‘zhangsan’ and age = ‘22’;

15、查询前5条数据
db.userInfo.find().limit(5);
相当于:selecttop 5 * from userInfo;

16、查询10条以后的数据
db.userInfo.find().skip(10);
相当于:select * from userInfo where id not in (
selecttop 10 * from userInfo
);

17、查询在5-10之间的数据
db.userInfo.find().limit(10).skip(5);
可用于分页,limit是pageSize,skip是第几页*pageSize

18、or与 查询
db.userInfo.find({$or: [{age: 22}, {age: 25}]});
相当于:select * from userInfo where age = 22 or age = 25;

19、查询第一条数据
db.userInfo.findOne();
相当于:selecttop 1 * from userInfo;
db.userInfo.find().limit(1);

20、查询某个结果集的记录条数
db.userInfo.find({age: {$gte: 25}}).count();
相当于:select count(*) from userInfo where age >= 20;

21、按照某列进行排序
db.userInfo.find({sex: {$exists: true}}).count();
相当于:select count(sex) from userInfo;
索引

1、创建索引
db.userInfo.ensureIndex({name: 1});
db.userInfo.ensureIndex({name: 1, ts: -1});

2、查询当前聚集集合所有索引
db.userInfo.getIndexes();

3、查看总索引记录大小
db.userInfo.totalIndexSize();

4、读取当前集合的所有index信息
db.users.reIndex();

5、删除指定索引
db.users.dropIndex("name_1");

6、删除所有索引索引
db.users.dropIndexes();
修改、添加、删除集合数据

1、添加
db.users.save({name: ‘zhangsan’, age: 25, sex: true});
添加的数据的数据列,没有固定,根据添加的数据为准

2、修改
db.users.update({age: 25}, {$set: {name: 'changeName'}}, false, true);
相当于:update users set name = ‘changeName’ where age = 25;

db.users.update({name: 'Lisi'}, {$inc: {age: 50}}, false, true);
相当于:update users set age = age + 50 where name = ‘Lisi’;

db.users.update({name: 'Lisi'}, {$inc: {age: 50}, $set: {name: 'hoho'}}, false, true);
相当于:update users set age = age + 50, name = ‘hoho’ where name = ‘Lisi’;

3、删除
db.users.remove({age: 132});

4、查询修改删除
db.users.findAndModify({
query: {age: {$gte: 25}},
sort: {age: -1},
update: {$set: {name: 'a2'}, $inc: {age: 2}},
remove: true
});

db.runCommand({ findandmodify : "users",
query: {age: {$gte: 25}},
sort: {age: -1},
update: {$set: {name: 'a2'}, $inc: {age: 2}},
remove: true
});
update 或 remove 其中一个是必须的参数; 其他参数可选。

参数
详解
默认值

query
查询过滤条件
{}

sort
如果多个文档符合查询过滤条件,将以该参数指定的排列方式选择出排在首位的对象,该对象将被操作
{}

remove
若为true,被选中对象将在返回前被删除
N/A

update
一个 修改器对象
N/A

new
若为true,将返回修改后的对象而不是原始对象。在删除操作中,该参数被忽略。
false

fields
参见Retrieving a Subset of Fields (1.5.0+)
All fields

upsert
创建新对象若查询结果为空。 示例 (1.5.4+)
false

语句块操作
1、简单Hello World
print("Hello World!");
这种写法调用了print函数,和直接写入"Hello World!"的效果是一样的;

2、将一个对象转换成json
tojson(new Object());
tojson(new Object('a'));

3、循环添加数据

for (var i = 0; i < 30; i++) {
... db.users.save({name: "u_" + i, age: 22 + i, sex: i % 2});
... };
这样就循环添加了30条数据,同样也可以省略括号的写法
for (var i = 0; i < 30; i++) db.users.save({name: "u_" + i, age: 22 + i, sex: i % 2});
也是可以的,当你用db.users.find()查询的时候,显示多条数据而无法一页显示的情况下,可以用it查看下一页的信息;

4、find 游标查询

var cursor = db.users.find();
while (cursor.hasNext()) {
printjson(cursor.next());
}
这样就查询所有的users信息,同样可以这样写
var cursor = db.users.find();
while (cursor.hasNext()) { printjson(cursor.next); }
同样可以省略{}号

5、forEach迭代循环
db.users.find().forEach(printjson);
forEach中必须传递一个函数来处理每条迭代的数据信息

6、将find游标当数组处理
var cursor = db.users.find();
cursor[4];
取得下标索引为4的那条数据
既然可以当做数组处理,那么就可以获得它的长度:cursor.length();或者cursor.count();
那样我们也可以用循环显示数据
for (var i = 0, len = c.length(); i < len; i++) printjson(c[i]);

7、将find游标转换成数组

var arr = db.users.find().toArray();
printjson(arr[2]);
用toArray方法将其转换为数组

8、定制我们自己的查询结果
只显示age <= 28的并且只显示age这列数据
db.users.find({age: {$lte: 28}}, {age: 1}).forEach(printjson);
db.users.find({age: {$lte: 28}}, {age: true}).forEach(printjson);
排除age的列
db.users.find({age: {$lte: 28}}, {age: false}).forEach(printjson);

9、forEach传递函数显示信息
db.things.find({x:4}).forEach(function(x) {print(tojson(x));});

node.js之fs模块

Node.js的文件系统的Api
//公共引用
var fs = require('fs'),
path = require('path');
1、读取文件readFile函数
//readFile(filename,[options],callback);

/**

  • filename, 必选参数,文件名
  • [options],可选参数,可指定flag(文件操作选项,如r+ 读写;w+ 读写,文件不存在则创建)及encoding属性
  • callback 读取文件后的回调函数,参数默认第一个err,第二个data 数据
    */

fs.readFile(__dirname + '/test.txt', {flag: 'r+', encoding: 'utf8'}, function (err, data) {
if(err) {
console.error(err);
return;
}
console.log(data);
});
2、写文件
// fs.writeFile(filename,data,[options],callback);
var w_data = '这是一段通过fs.writeFile函数写入的内容;\r\n';
var w_data = new Buffer(w_data);

/**

  • filename, 必选参数,文件名
  • data, 写入的数据,可以字符或一个Buffer对象
  • [options],flag,mode(权限),encoding
  • callback 读取文件后的回调函数,参数默认第一个err,第二个data 数据
    */

fs.writeFile(__dirname + '/test.txt', w_data, {flag: 'a'}, function (err) {
if(err) {
console.error(err);
} else {
console.log('写入成功');
}
});
3、以追加方式写文件
// fs.appendFile(filename,data,[options],callback);

fs.appendFile(__dirname + '/test.txt', '使用fs.appendFile追加文件内容', function () {
console.log('追加内容完成');
});
4、打开文件
// fs.open(filename, flags, [mode], callback);

/**

  • filename, 必选参数,文件名
  • flags, 操作标识,如"r",读方式打开
  • [mode],权限,如777,表示任何用户读写可执行
  • callback 打开文件后回调函数,参数默认第一个err,第二个fd为一个整数,表示打开文件返回的文件描述符,window中又称文件句柄
    */

fs.open(__dirname + '/test.txt', 'r', '0666', function (err, fd) {
console.log(fd);
});
5、读文件,读取打开的文件内容到缓冲区中;
//fs.read(fd, buffer, offset, length, position, callback);
/**

  • fd, 使用fs.open打开成功后返回的文件描述符
  • buffer, 一个Buffer对象,v8引擎分配的一段内存
  • offset, 整数,向缓存区中写入时的初始位置,以字节为单位
  • length, 整数,读取文件的长度
  • position, 整数,读取文件初始位置;文件大小以字节为单位
  • callback(err, bytesRead, buffer), 读取执行完成后回调函数,bytesRead实际读取字节数,被读取的缓存区对象
    */

fs.open(__dirname + '/test.txt', 'r', function (err, fd) {
if(err) {
console.error(err);
return;
} else {
var buffer = new Buffer(255);
console.log(buffer.length);
//每一个汉字utf8编码是3个字节,英文是1个字节
fs.read(fd, buffer, 0, 9, 3, function (err, bytesRead, buffer) {
if(err) {
throw err;
} else {
console.log(bytesRead);
console.log(buffer.slice(0, bytesRead).toString());
//读取完后,再使用fd读取时,基点是基于上次读取位置计算;
fs.read(fd, buffer, 0, 9, null, function (err, bytesRead, buffer) {
console.log(bytesRead);
console.log(buffer.slice(0, bytesRead).toString());
});
}
});
}
});
6、写文件,将缓冲区内数据写入使用fs.open打开的文件
//fs.write(fd, buffer, offset, length, position, callback);

/**

  • fd, 使用fs.open打开成功后返回的文件描述符
  • buffer, 一个Buffer对象,v8引擎分配的一段内存
  • offset, 整数,从缓存区中读取时的初始位置,以字节为单位
  • length, 整数,从缓存区中读取数据的字节数
  • position, 整数,写入文件初始位置;
  • callback(err, written, buffer), 写入操作执行完成后回调函数,written实际写入字节数,buffer被读取的缓存区对象
    */

fs.open(__dirname + '/test.txt', 'a', function (err, fd) {
if(err) {
console.error(err);
return;
} else {
var buffer = new Buffer('写入文件数据内容');
//写入'入文件'三个字
fs.write(fd, buffer, 3, 9, 12, function (err, written, buffer) {
if(err) {
console.log('写入文件失败');
console.error(err);
return;
} else {
console.log(buffer.toString());
//写入'数据内'三个字
fs.write(fd, buffer, 12, 9, null, function (err, written, buffer) {
console.log(buffer.toString());
})
}
});
}
});
7、刷新缓存区;
// 使用fs.write写入文件时,操作系统是将数据读到内存,再把数据写入到文件中,当数据读完时并不代表数据已经写完,因为有一部分还可能在内在缓冲区内。
// 因此可以使用fs.fsync方法将内存中数据写入文件;--刷新内存缓冲区;

//fs.fsync(fd, [callback])
/**

  • fd, 使用fs.open打开成功后返回的文件描述符
  • [callback(err, written, buffer)], 写入操作执行完成后回调函数,written实际写入字节数,buffer被读取的缓存区对象
    */

fs.open(__dirname + '/test.txt', 'a', function (err, fd) {
if(err)
throw err;
var buffer = new Buffer('我爱nodejs编程');
fs.write(fd, buffer, 0, 9, 0, function (err, written, buffer) {
console.log(written.toString());
fs.write(fd, buffer, 9, buffer.length - 9, null, function (err, written) {
console.log(written.toString());
fs.fsync(fd);
fs.close(fd);
})
});
});
8、创建目录;
//使用fs.mkdir创建目录
//fs.mkdir(path, [mode], callback);

/**

  • path, 被创建目录的完整路径及目录名;
  • [mode], 目录权限,默认0777
  • [callback(err)], 创建完目录回调函数,err错误对象
    */

fs.mkdir(__dirname + '/fsDir', function (err) {
if(err)
throw err;
console.log('创建目录成功')
});
9、读取目录;
//使用fs.readdir读取目录,重点其回调函数中files对象
//fs.readdir(path, callback);

/**

  • path, 要读取目录的完整路径及目录名;
  • [callback(err, files)], 读完目录回调函数;err错误对象,files数组,存放读取到的目录中的所有文件名
    */

fs.readdir(__dirname + '/fsDir/', function (err, files) {
if(err) {
console.error(err);
return;
} else {
files.forEach(function (file) {
var filePath = path.normalize(__dirname + '/fsDir/' + file);
fs.stat(filePath, function (err, stat) {
if(stat.isFile()) {
console.log(filePath + ' is: ' + 'file');
}
if(stat.isDirectory()) {
console.log(filePath + ' is: ' + 'dir');
}
});
});
for (var i = 0; i < files.length; i++) {
//使用闭包无法保证读取文件的顺序与数组中保存的致
(function () {
var filePath = path.normalize(__dirname + '/fsDir/' + files[i]);
fs.stat(filePath, function (err, stat) {
if(stat.isFile()) {
console.log(filePath + ' is: ' + 'file');
}
if(stat.isDirectory()) {
console.log(filePath + ' is: ' + 'dir');
}
});
})();
}
}
});
10、查看文件与目录的信息;
//fs.stat(path, callback);
//fs.lstat(path, callback); //查看符号链接文件
/**

  • path, 要查看目录/文件的完整路径及名;
  • [callback(err, stats)], 操作完成回调函数;err错误对象,stat fs.Stat一个对象实例,提供如:isFile, isDirectory,isBlockDevice等方法及size,ctime,mtime等属性
    */

//实例,查看fs.readdir
11、查看文件与目录的是否存在
//fs.exists(path, callback);

/**

  • path, 要查看目录/文件的完整路径及名;
  • [callback(exists)], 操作完成回调函数;exists true存在,false表示不存在
    */

fs.exists(__dirname + '/te', function (exists) {
var retTxt = exists ? retTxt = '文件存在' : '文件不存在';
console.log(retTxt);
});
12、修改文件访问时间与修改时间
//fs.utimes(path, atime, mtime, callback);

/**

  • path, 要查看目录/文件的完整路径及名;
  • atime, 新的访问时间
  • ctime, 新的修改时间
  • [callback(err)], 操作完成回调函数;err操作失败对象
    */

fs.utimes(__dirname + '/test.txt', new Date(), new Date(), function (err) {
if(err) {
console.error(err);
return;
}
fs.stat(__dirname + '/test.txt', function (err, stat) {
console.log('访问时间: ' + stat.atime.toString() + '; \n修改时间:' + stat.mtime);
console.log(stat.mode);
})
});
13、修改文件或目录的操作权限
//fs.utimes(path, mode, callback);

/**

  • path, 要查看目录/文件的完整路径及名;
  • mode, 指定权限,如:0666 8进制,权限:所有用户可读、写,
  • [callback(err)], 操作完成回调函数;err操作失败对象
    */

fs.chmod(__dirname + '/fsDir', 0666, function (err) {
if(err) {
console.error(err);
return;
}
console.log('修改权限成功')
});
14、移动/重命名文件或目录
//fs.rename(oldPath, newPath, callback);

/**

  • oldPath, 原目录/文件的完整路径及名;
  • newPath, 新目录/文件的完整路径及名;如果新路径与原路径相同,而只文件名不同,则是重命名
  • [callback(err)], 操作完成回调函数;err操作失败对象
    */
    fs.rename(__dirname + '/test', __dirname + '/fsDir', function (err) {
    if(err) {
    console.error(err);
    return;
    }
    console.log('重命名成功')
    });
    15、删除空目录
    //fs.rmdir(path, callback);

/**

  • path, 目录的完整路径及目录名;
  • [callback(err)], 操作完成回调函数;err操作失败对象
    */

fs.rmdir(__dirname + '/test', function (err) {
fs.mkdir(__dirname + '/test', 0666, function (err) {
console.log('创建test目录');
});
if(err) {
console.log('删除空目录失败,可能原因:1、目录不存在,2、目录不为空')
console.error(err);
return;
}
console.log('删除空目录成功!');
});
16、监视文件
//对文件进行监视,并且在监视到文件被修改时执行处理
//fs.watchFile(filename, [options], listener);

/**

  • filename, 完整路径及文件名;
  • [options], persistent true表示持续监视,不退出程序;interval 单位毫秒,表示每隔多少毫秒监视一次文件
  • listener, 文件发生变化时回调,有两个参数:curr为一个fs.Stat对象,被修改后文件,prev,一个fs.Stat对象,表示修改前对象
    */

fs.watchFile(__dirname + '/test.txt', {interval: 20}, function (curr, prev) {
if(Date.parse(prev.ctime) == 0) {
console.log('文件被创建!');
} else if(Date.parse(curr.ctime) == 0) {
console.log('文件被删除!')
} else if(Date.parse(curr.mtime) != Date.parse(prev.mtime)) {
console.log('文件有修改');
}
});
fs.watchFile(__dirname + '/test.txt', function (curr, prev) {
console.log('这是第二个watch,监视到文件有修改');
});
17、取消监视文件
//取消对文件进行监视
//fs.unwatchFile(filename, [listener]);

/**

  • filename, 完整路径及文件名;
  • [listener], 要取消的监听器事件,如果不指定,则取消所有监听处理事件
    */

var listener = function (curr, prev) {
console.log('我是监视函数')
}
fs.unwatchFile(__dirname + '/test.txt', listener);
18、监视文件或目录
// 对文件或目录进行监视,并且在监视到修改时执行处理;
// fs.watch返回一个fs.FSWatcher对象,拥有一个close方法,用于停止watch操作;
// 当fs.watch有文件变化时,会触发fs.FSWatcher对象的change(err, filename)事件,err错误对象,filename发生变化的文件名
// fs.watch(filename, [options], [listener]);

/**

  • filename, 完整路径及文件名或目录名;
  • [listener(event, filename], 监听器事件,有两个参数:event 为rename表示指定的文件或目录中有重命名、删除或移动操作或change表示有修改,filename表示发生变化的文件路径
    */

var fsWatcher = fs.watch(__dirname + '/test', function (event, filename) {
//console.log(event)
});

//console.log(fsWatcher instanceof FSWatcher);

fsWatcher.on('change', function (event, filename) {
console.log(filename + ' 发生变化')
});

//30秒后关闭监视
setTimeout(function () {
console.log('关闭')
fsWatcher.close(function (err) {
if(err) {
console.error(err)
}
console.log('关闭watch')
});
}, 30000);
19、文件流
/*

  • 流,在应用程序中表示一组有序的、有起点有终点的字节数据的传输手段;
  • Node.js中实现了stream.Readable/stream.Writeable接口的对象进行流数据读写;以上接口都继承自EventEmitter类,因此在读/写流不同状态时,触发不同事件;
  • 关于流读取:Node.js不断将文件一小块内容读入缓冲区,再从缓冲区中读取内容;
  • 关于流写入:Node.js不断将流数据写入内在缓冲区,待缓冲区满后再将缓冲区写入到文件中;重复上面操作直到要写入内容写写完;
  • readFile、read、writeFile、write都是将整个文件放入内存而再操作,而则是文件一部分数据一部分数据操作;
    *
  • -----------------------流读取-------------------------------------
  • 读取数据对象:
  • fs.ReadStream 读取文件
  • http.IncomingMessage 客户端请求或服务器端响应
  • net.Socket Socket端口对象
  • child.stdout 子进程标准输出
  • child.stdin 子进程标准入
  • process.stdin 用于创建进程标准输入流
  • Gzip、Deflate、DeflateRaw 数据压缩
    *
  • 触发事件:
  • readable 数据可读时
  • data 数据读取后
  • end 数据读取完成时
  • error 数据读取错误时
  • close 关闭流对象时
    *
  • 读取数据的对象操作方法:
  • read 读取数据方法
  • setEncoding 设置读取数据的编
  • pause 通知对象众目停止触发data事件
  • resume 通知对象恢复触发data事件
  • pipe 设置数据通道,将读入流数据接入写入流;
  • unpipe 取消通道
  • unshift 当流数据绑定一个解析器时,此方法取消解析器
    *
  • ------------------------流写入-------------------------------------
  • 写数据对象:
  • fs.WriteStream 写入文件对象
  • http.clientRequest 写入HTTP客户端请求数据
  • http.ServerResponse 写入HTTP服务器端响应数据
  • net.Socket 读写TCP流或UNIX流,需要connection事件传递给用户
  • child.stdout 子进程标准输出
  • child.stdin 子进程标准入
  • Gzip、Deflate、DeflateRaw 数据压缩
    *
  • 写入数据触发事件:
  • drain 当write方法返回false时,表示缓存区中已经输出到目标对象中,可以继续写入数据到缓存区
  • finish 当end方法调用,全部数据写入完成
  • pipe 当用于读取数据的对象的pipe方法被调用时
  • unpipe 当unpipe方法被调用
  • error 当发生错误
    *
  • 写入数据方法:
  • write 用于写入数据
  • end 结束写入,之后再写入会报错;
    /
    20、创建读取流
    //fs.createReadStream(path, [options])
    /
    *
  • path 文件路径
  • [options] flags:指定文件操作,默认'r',读操作;encoding,指定读取流编码;autoClose, 是否读取完成后自动关闭,默认true;start指定文件开始读取位置;end指定文件开始读结束位置
    */

var rs = fs.createReadStream(__dirname + '/test.txt', {start: 0, end: 2});
//open是ReadStream对象中表示文件打开时事件,
rs.on('open', function (fd) {
console.log('开始读取文件');
});

rs.on('data', function (data) {
console.log(data.toString());
});

rs.on('end', function () {
console.log('读取文件结束')
});
rs.on('close', function () {
console.log('文件关闭');
});

rs.on('error', function (err) {
console.error(err);
});

//暂停和回复文件读取;
rs.on('open', function () {
console.log('开始读取文件');
});

rs.pause();

rs.on('data', function (data) {
console.log(data.toString());
});

setTimeout(function () {
rs.resume();
}, 2000);
21、创建写入流
//fs.createWriteStream(path, [options])
/**

  • path 文件路径
  • [options] flags:指定文件操作,默认'w',;encoding,指定读取流编码;start指定写入文件的位置
    */

/* ws.write(chunk, [encoding], [callback]);

  • chunk, 可以为Buffer对象或一个字符串,要写入的数据
  • [encoding], 编码
  • [callback], 写入后回调
    */

/* ws.end([chunk], [encoding], [callback]);

  • [chunk], 要写入的数据
  • [encoding], 编码
  • [callback], 写入后回调
    */

var ws = fs.createWriteStream(__dirname + '/test.txt', {start: 0});
var buffer = new Buffer('我也喜欢你');
ws.write(buffer, 'utf8', function (err, buffer) {
console.log(arguments);
console.log('写入完成,回调函数没有参数')
});
//最后再写入的内容
ws.end('再见');
//使用流完成复制文件操作
var rs = fs.createReadStream(__dirname + '/test.txt')
var ws = fs.createWriteStream(__dirname + '/test/test.txt');

rs.on('data', function (data) {
ws.write(data)
});

ws.on('open', function (fd) {
console.log('要写入的数据文件已经打开,文件描述符是: ' + fd);
});

rs.on('end', function () {
console.log('文件读取完成');
ws.end('完成', function () {
console.log('文件全部写入完成')
});
});

//关于WriteStream对象的write方法返回一个布尔类型,当缓存区中数据全部写满时,返回false;
//表示缓存区写满,并将立即输出到目标对象中

//第一个例子
var ws = fs.createWriteStream(__dirname + '/test/test.txt');
for (var i = 0; i < 10000; i++) {
var w_flag = ws.write(i.toString());
//当缓存区写满时,输出false
console.log(w_flag);
}

//第二个例子
var ws = fs.createWriteStream(__dirname + '/test/untiyou.mp3');
var rs = fs.createReadStream(__dirname + '/test/Until You.mp3');
rs.on('data', function (data) {
var flag = ws.write(data);
console.log(flag);
});

//系统缓存区数据已经全部输出触发drain事件
ws.on('drain', function () {
console.log('系统缓存区数据已经全部输出。')
});
22、管道pipe实现流读写
//rs.pipe(destination, [options]);
/**

  • destination 必须一个可写入流数据对象
  • [opations] end 默认为true,表示读取完成立即关闭文件;
    */

var rs = fs.createReadStream(__dirname + '/test/Until You.mp3');
var ws = fs.createWriteStream(__dirname + '/test/untiyou.mp3');
rs.pipe(ws);
rs.on('data', function (data) {
console.log('数据可读')
});
rs.on('end', function () {
console.log('文件读取完成');
//ws.end('再见')
});

webpack配置

每个项目下都必须配置有一个 webpack.config.js ,它的作用如同常规的 gulpfile.js/Gruntfile.js ,就是一个配置项,告诉 webpack 它需要做什么。
一. 总览

var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');

module.exports = {
    //插件项
    plugins: [commonsPlugin],
    //页面入口文件配置
    entry: {
        index : './src/js/page/index.js'
    },
    //入口文件输出配置
    output: {
        path: 'dist/js/page',
        filename: '[name].js'
    },
    module: {
        //加载器配置
        loaders: [
            { test: /\.css$/, loader: 'style-loader!css-loader' },
            { test: /\.js$/, loader: 'jsx-loader?harmony' },
            { test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
            { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
        ]
    },
    //其它解决方案配置
    resolve: {
        root: 'E:/github/flux-example/src', //绝对路径
        extensions: ['', '.js', '.json', '.scss'],
        alias: {
            AppStore : 'js/stores/AppStores.js',
            ActionType : 'js/actions/ActionType.js',
            AppAction : 'js/actions/AppAction.js'
        }
    }
};

⑴ plugins 是插件项,这里我们使用了一个 CommonsChunkPlugin 的插件,它用于提取多个入口文件的公共脚本部分,然后生成一个 common.js 来方便多页面之间进行复用。

⑵ entry 是页面入口文件配置,output 是对应输出项配置(即入口文件最终要生成什么名字的文件、存放到哪里),其语法大致为:

{
    entry: {
        page1: "./page1",
        //支持数组形式,将加载数组中的所有模块,但以最后一个模块作为输出
        page2: ["./entry1", "./entry2"]
    },
    output: {
        path: "dist/js/page",
        filename: "[name].bundle.js"
    }
}

该段代码最终会生成一个 page1.bundle.js 和 page2.bundle.js,并存放到 ./dist/js/page 文件夹下。

⑶ module.loaders 是最关键的一块配置。它告知 webpack 每一种文件都需要使用什么加载器来处理:

module: {
        //加载器配置
        loaders: [
            //.css 文件使用 style-loader 和 css-loader 来处理
            { test: /\.css$/, loader: 'style-loader!css-loader' },
            //.js 文件使用 jsx-loader 来编译处理
            { test: /\.js$/, loader: 'jsx-loader?harmony' },
            //.scss 文件使用 style-loader、css-loader 和 sass-loader 来编译处理
            { test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
            //图片文件使用 url-loader 来处理,小于8kb的直接转为base64
            { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
        ]
    }

如上,"-loader"其实是可以省略不写的,多个loader之间用“!”连接起来。

注意所有的加载器都需要通过 npm 来加载,并建议查阅它们对应的 readme 来看看如何使用。

拿最后一个 url-loader 来说,它会将样式中引用到的图片转为模块来处理,使用该加载器需要先进行安装:npm install url-loader -save-dev

配置信息的参数“?limit=8192”表示将所有小于8kb的图片都转为base64形式(其实应该说超过8kb的才使用 url-loader 来映射到文件,否则转为data url形式)。
全部loader列表 http://webpack.github.io/docs/list-of-loaders.html

⑷ 最后是 resolve 配置,这块很好理解,直接写注释了:

resolve: {
        //查找module的话从这里开始查找
        root: 'E:/github/flux-example/src', //绝对路径
        //自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
        extensions: ['', '.js', '.json', '.scss'],
        //模块别名定义,方便后续直接引用别名,无须多写长长的地址
        alias: {
            AppStore : 'js/stores/AppStores.js',//后续直接 require('AppStore') 即可
            ActionType : 'js/actions/ActionType.js',
            AppAction : 'js/actions/AppAction.js'
        }
    }

更多详细配置 http://webpack.github.io/docs/configuration.html

自定义公共模块提取

在文章开始我们使用了 CommonsChunkPlugin 插件来提取多个页面之间的公共模块,并将该模块打包为 common.js 。

但有时候我们希望能更加个性化一些,我们可以这样配置:

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        p3: "./page3",
        ap1: "./admin/page1",
        ap2: "./admin/page2"
    },
    output: {
        filename: "[name].js"
    },
    plugins: [
        new CommonsChunkPlugin("admin-commons.js", ["ap1", "ap2"]),
        new CommonsChunkPlugin("commons.js", ["p1", "p2", "admin-commons.js"])
    ]
};
// <script>s required:
// page1.html: commons.js, p1.js
// page2.html: commons.js, p2.js
// page3.html: p3.js
// admin-page1.html: commons.js, admin-commons.js, ap1.js
// admin-page2.html: commons.js, admin-commons.js, ap2.js

有时候可能希望项目的样式能不要被打包到脚本中,而是独立出来作为.css,然后在页面中以标签引入。这时候我们需要 extract-text-webpack-plugin 来帮忙:

 var webpack = require('webpack');
    var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
    var ExtractTextPlugin = require("extract-text-webpack-plugin");

    module.exports = {
        plugins: [commonsPlugin, new ExtractTextPlugin("[name].css")],
        entry: {
        //...省略其它配置

使用CDN/远程文件

有时候我们希望某些模块走CDN并以<script>的形式挂载到页面上来加载,但又希望能在 webpack 的模块中使用上。

这时候我们可以在配置文件里使用 externals 属性来帮忙:
{
externals: {
// require("jquery") 是引用自外部模块的
// 对应全局变量 jQuery
"jquery": "jQuery"
}
}

需要留意的是,得确保 CDN 文件必须在 webpack 打包文件引入之前先引入。

我们倒也可以使用 script.js 在脚本中来加载我们的模块:

var $script = require("scriptjs");
$script("//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js", function() {
  $('body').html('It works!')
});

Cluster

Node.js是单线程、异步非阻塞模型,所带来的优点就不再叙述了,来谈谈缺点和改进的空间。现在是多核心时代,服务器动不动就是4核以上,对于单线程模型来说,无法充分利用多CPU的结构。但这不应该影响到node.js的发展,请参考Nginx,Nodejs也在积极改进当中,Cluster正是基于这种现实情况而诞生的,通过cluster模块创建多进程,来实现cpu的充分利用。

var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// Fork 工作进程
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('工作进程 ' + worker.process.pid + ' 被终止');
});
} else {
// 工作进程可以共享任何 TCP 连接
// 本例中为 HTTP 服务器
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
}

master是总控节点,worker是运行节点。然后根据CPU的数量,启动worker。
每个worker进程通过使用child_process.fork()函数,基于IPC(Inter-Process Communication,进程间通信),实现与master进程间通信。

当多个进程都在 accept() 同样的资源的时候,操作系统的负载均衡非常高效。Node.js没有路由逻辑,worker之间没有共享状态。所以,程序要设计得简单一些,比如基于内存的session。
因为workers都是独力运行的,根据程序的需要,它们可以被独立删除或者重启,worker并不相互影响。只要还有workers存活,则master将继续接收连接。Node不会自动维护workers的数目。
cluster的负载均衡的策略,应该是随机分配的。由系统来决定将该请求交给哪个进程进行处理。这种完全依赖于系统的负载均衡存在着一个重要缺陷:在windows,linux和Solaris上,只要某个子进程的accept queue为空(通常为最后创建的那个子进程),系统就会将多个connetion分配到同一个子进程上,这会造成进程间负载极为不均衡。特别是在使用长连接的时候,单位时间内的new coming connection并不高,子进程的accept queue往往均为空,就会导致connection会不停的分配给同一个进程。所以这种负载均衡完全依赖于accept queue的空闲程度,只有在使用短连接,而且并发非常高的情况下,才能达到负载均衡,但是这个时候系统的load会非常高,系统也会变得不稳定起来。

进程间数据共享

var cluster = require('cluster');
var data = 0;//这里定义数据不会被所有进程共享,各个进程有各自的内存区域
if (cluster.isMaster) { //主进程
var numCPUs = require('os').cpus().length;
for (var i = 0; i < numCPUs; i++) {
var worker = cluster.fork();
}
data++;
console.log('DATA VALUE in MainProcess: %d ' , data);
} else { //子进程,会被调用numCPUs次
data++;
console.log('DATA VALUE in ChildProcess %d: %d ' cluster.worker.id, data);
}

因为每个进程在内存都有各自的区域,因此data++操作是在各自的区域内进行的,也就是说变量data没被共享。那么怎么来在各进程之间共享数据呢?

var cluster = require('cluster');
var http = require('http');

if (cluster.isMaster) {
var numCPUs = require('os').cpus().length;
var data = 0;
// 启动多个进程.
for (var i = 0; i < numCPUs; i++) {
//增加一个进程
var worker_process = cluster.fork();
//侦听子进程的message事件
worker_process.on('message', function(msg) {
if (msg.cmd && msg.cmd == 'notifyRequest') {
data++;
console.log('DATA VALUE : %d ', data);
}
});
}
} else {
process.send({ cmd: 'notifyRequest' });
}

如果需要共享数据,需要在进程间使用消息通知来达到这个目的。

技术选型

框架与库

库(lib)具有以下三个特点:

1、是针对特定问题的解答,具有专业性
2、不控制应用的流程
3、被动的被调用

框架(frameword)具有以下三个特点:
1、具有控制反转(inverse of control)的功能
2、决定应用程序的生命周期
3、一般来说,集成了大量的库

框架会在特定的时间要求程序执行某段代码。框架决定了什么时候调用库,决定了什么时候要求代码去执行特定功能

而实际上,一个库有时也可以称之为框架,而库里面集成的方法称之为库

框架和库的区别不由实际大小决定,而由思考角度来决定。框架和库实际上可以统称为解决方案

解决方案

前端开发中的解决方案主要用于解决以下7个方面的问题:

1、DOM

2、Communication(通信)

3、Utililty(工具库)

4、Templating(模板集成)

5、Component(组件)

6、Routing(路由)

7、Architecture(架构)

【why】

为什么要使用外部的解决方案呢?

1、提高开发效率

2、可靠性高(浏览器兼容,测试覆盖)

3、配备优良的配套,如文档、DEMO及工具等

4、代码设计的更合理、更优雅

5、专业性高

如果问题过于简单,或者备选框架的质量和可靠性无法保证,再或者无法满足业务需求,则不应该选择外部的框架。如果团队中已经有相关的积累,就更不需要使用了

【how】

一般地,解决方案要实际开发中有以下3种使用方式:

1、开放式:基于外部模块系统,并自由组合

2、半开放式:基于一个定制的模块系统,内部外部解决方案共存

3、封闭式:深度定制的模块系统,很少需要引入外部模块

DOM

接下来,将针对解决方案中提到的7个问题进行分别介绍,首先是DOM

关于DOM,主要包括Selector(选择器)、Manipulation(DOM操作)、Event(事件)、Animation(动画)这四个部分

DOM相关的解决方案主要用于提供以下操作 

1、提供便利的 DOM 查询、操作、移动等操作

2、提供事件绑定及事件代理支持

3、提供浏览器特性检测及 UserAgent 侦测

4、提供节点属性、样式、类名的操作

5、保证目标平台的跨浏览器支持

【常用方案】

常用的DOM解决方案有 jQuery、zepto.JS、MOOTOO.JS等

jQuery是曾经风靡一时的最流行的前端解决方案,jQuery特有的链式调用的方式简化了javascript的复杂操作,而且使人们不再需要关心兼容性,并提供了大量的实用方法

zepto是jQuery的精简版,针对移动端去除了大量jQuery的兼容代码,提供了简单的手势,部分API的实现方式不同

mootools源码清晰易懂,严格遵循Command-Query(命令-查询)的接口规范,没有诸如jQuery的两义性接口。还有一个不得不提的特点是,使用选择器获取的是DOM原生对象,而不是被包装过的对象。而它支持的诸多方法则是通过直接扩展DOM原生对象实现的,这也是它的争议所在

相比较而言,最稳妥的DOM解决方案是jQuery

【专业领域】

上面的解决方案用于解决DOM一般的通用问题。随着技术的发展,DOM的专业领域出现一些小而精致的解决方案

1、手势

Hammer.JS包括了常见手势封装(Tab、Hold、Transform、Swifp)并支持自定义扩展

2、局部滚动

iscroll.JS是移动端position:fix + overflow:scroll的救星

3、高级动画

Velocity.JS可以复杂动画序列实现,不仅局限于 DOM

4、视频播放

Video.JS类似原生 video 标签的使用方式,对低级浏览器使用 flash 播放器

通信

关于通信,主要包括XMLHttpRequest、Form、JSONP、Socket等

通信相关的解决方案主要用于提供以下操作

1、处理与服务器的请求与相应

2、预处理请求数据与响应数据 Error/Success 的判断封装

3、多类型请求,统一接口(XMLHttpRequest1/2、JSONP、iFrame)

4、处理浏览器兼容性

【常用方案】

除了jQuery等,其他常用的通信解决方案有Reqwest、qwest等

Reqwest支持JSONP,稳定性高,IE6+支持,CORS 跨域,Promise/A 支持

qwest代码少、支持XMLHttpRequest2、CORS 跨域、支持高级数据类型(ArrayBuffer、Blob、FormData)

【专业领域】

对于实时性要求较高的需求可以使用socket.io,它实时性高,支持二进制数据流,智能自动回退支持,且支持多种后端语言

工具包

工具包(Utililty)的主要职责包括以下:

1、提供 JavaScript 原生不提供的功能

2、包装原生方法,使其便于使用

3、异步队列及流程控制

【常用方案】

常用的工具包解决方案有es5-shim、es6-shim、underscore、Lodash等  

上面提到的shim,也是经常听到的一个词,翻译过来是垫片的意思。对于es5、es6等标准包括的一些新方法,由于浏览器兼容性不高,所以无法直接使用它们。这时,就需要在保证实现与规范一致的基础上,来扩展原型方法,这种做法就叫做shim。好处在于,实际上就是在使用javascript的语法,但不用去考虑低版本浏览器的兼容性问题

es5-shim 提供 ES3 环境下的 ES5 支持

es6-shim 提供 ES5 环境下的 ES6支持

underscore 提供兼容 IE6+ 的扩展功能函数

Lodash是underscore 的高性能版本,方法多为 runtime 编译出来的

模板

模板主要包括三类:基于字符串的模板(String-based)、基于DOM的模板(DOM-based)、活动模板(Living Template)

1、基于字符串的模板(String-based),解决方案包括(dustjs、hogan.js、dot.js)

原理如下:输入一段模板字符串,通过编译之后 ,生成一段Function,通过Function的render或类render函数渲染输入的数据data,输出模板字符串,字符串通过innerHTML或类似的方式渲染成最后的DOM结构。这类模板的问题在于通过字符串生成DOM之后就不再变化,如果在改变输入的数据data,需要重新render,重新生成一个全新的DOM结构,性能较差。但该模板可以在服务器端运行

2、基于DOM的模板(DOM-based),解决方案包括(angularjs、vuejs、knockout)

原理如下:将输入的字符串模板通过innerHTML转换为一个无状态DOM树,然后遍历该节点树,去抓取关键属性或语句,来进行相关的绑定,进而变成了有状态的DOM树,最终导致DOM树会与数据模型model进行绑定。这类模板的特点是修改数据时,会使有状态的DOM树实时更新,运行时性能更好,也会保留 DOM 中的已有事件

3、活动模板(Living Template),解决方案包括(RegularJS、RactiveJS、htmlbar)

原理如下:活动模板融合了字符串模板和DOM模板的技术,模板字符串string通过自定义的解析器DSL-based Parse解析成AST(抽象语法树),通过遍历AST,使用createElement()、setAttribute()等原生DOM方法,生成DOM树,最终导致DOM树会与数据模型model进行绑定。由于其内部完全不使用innerHTML,所以安全性较高

组件

组件(Component)的主要职责包括以下:

1、提供基础的 CSS 支持
2、提供常见的组件,如slider、Modal等
3、提供声明式的调用方式(类似 Bootstrap)

【常用方案】

常用的组件解决方案有Bootstrap、Foundation等,两者具有移动端first的流式栅格系统,由sass组织,可定制UI

Bootstrap封装了常用的组件,是目前最火的组件解决方案

Foundation在国内知名度不高

路由

路由在单页系统中非常重要,主要职责如下

1、监听 URL 变化,并通知注册的模块
2、通过 JavaScript 进行主动跳转
3、历史管理
4、对目标浏览器的兼容性支持

【常用方案】

常用的路由解决方案有page.JS、Director.JS、Stateman、crossroad.JS等

page.JS类似 Express.Router 的路由规则的前端路由库

Director.JS可以前后端使用同一套规则定义路由

Stateman处理深层复杂路由的独立路优库

crossroad.JS老牌路由库,API 功能较为繁琐

架构

所有的架构(architecture)都是一个目的,就是解耦。解耦有很多方式,可以通过事件、分层等

市面上,有很多架构模式,包括MVC、MVVM、MV*等

架构的职责主要包括以下:

1、提供一种范式帮助(强制)开发者进行模块解耦

2、视图与模型分离

3、容易进行单元测试

4、容易实现应用扩展

以MVVM为例,如下图所示。它包括Model(数据层或模型层)、View(视图层)、ViewModel(控制层)

Model(数据层或模型层)表示数据实体,它们用于记录应用程序的数据

View(视图层)用于展示界面,界面是数据定制的反映,它包含样式结构定义以及VM享有的声明式数据以及数据绑定

ViewModel(控制层)是View与Model的粘合,它通过绑定事件与View交互并可以调用Service处理数据持久化,也可以通过数据绑定将Model的变动反映到View 中

它们的关系是:各部分之间的通信,都是双向的;View 与 Model 不发生联系,都通过 ViewModel 传递;View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而ViewModel非常厚,所有逻辑都部署在那里

【SPA】

要特点注意的是,MV* !== SPA(单页系统)

SPA应用程序的逻辑比较复杂,需要一种模式来进行解耦,但并不一定是MV*模式

最后

最后推荐一个框架选型网站https://www.javascripting.com,该网站根据不同的需求的选择,给出当下流行的框架选型

http协议等等。。

作为一个经常和web打交道的程序员,了解这些协议是必须的,本文就向大家介绍一下这些协议的区别和基本概念,文中可能不局限于前端知识,还包括一些运维,协议方面的知识,希望能给读者带来一些收获,如有不对之处还请指出。

  1. web始祖HTTP
    全称:超文本传输协议(HyperText Transfer Protocol) 伴随着计算机网络和浏览器的诞生,HTTP1.0也随之而来,处于计算机网络中的应用层,HTTP是建立在TCP协议之上,所以HTTP协议的瓶颈及其优化技巧都是基于TCP协议本身的特性,例如tcp建立连接的3次握手和断开连接的4次挥手以及每次建立连接带来的RTT延迟时间。

  2. HTTP与现代化浏览器
    早在HTTP建立之初,主要就是为了将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器。也是说对于前端来说,我们所写的HTML页面将要放在我们的web服务器上,用户端通过浏览器访问url地址来获取网页的显示内容,但是到了WEB2.0以来,我们的页面变得复杂,不仅仅单纯的是一些简单的文字和图片,同时我们的HTML页面有了CSS,Javascript,来丰富我们的页面展示,当ajax的出现,我们又多了一种向服务器端获取数据的方法,这些其实都是基于HTTP协议的。同样到了移动互联网时代,我们页面可以跑在手机端浏览器里面,但是和PC相比,手机端的网络情况更加复杂,这使得我们开始了不得不对HTTP进行深入理解并不断优化过程中。

  3. HTTP的基本优化
    影响一个HTTP网络请求的因素主要有两个:带宽和延迟。
    带宽:如果说我们还停留在拨号上网的阶段,带宽可能会成为一个比较严重影响请求的问题,但是现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了。
    延迟:
    浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制,后续请求就会被阻塞。
    DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。
    建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。

  4. HTTP1.0和HTTP1.1的一些区别
    HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议。 主要区别主要体现在:
    缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
    带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
    错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
    Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。
    长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。以下是常见的HTTP1.0:

区别用一张图来体现:

  1. HTTP1.0和1.1现存的一些问题
    上面提到过的,HTTP1.x在传输数据时,每次都需要重新建立连接,无疑增加了大量的延迟时间,特别是在移动端更为突出。
    HTTP1.x在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,这在一定程度上无法保证数据的安全性。
    HTTP1.x在使用时,header里携带的内容过大,在一定程度上增加了传输的成本,并且每次请求header基本不怎么变化,尤其在移动端增加用户流量。
    虽然HTTP1.x支持了keep-alive,来弥补多次创建连接产生的延迟,但是keep-alive使用多了同样会给服务端带来大量的性能压力,并且对于单个文件被不断请求的服务(例如图片存放网站),keep-alive可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。

  2. HTTPS应声而出
    为了解决以上问题,网景在1994年创建了HTTPS,并应用在网景导航者浏览器中。 最初,HTTPS是与SSL一起使用的;在SSL逐渐演变到TLS时(其实两个是一个东西,只是名字不同而已),最新的HTTPS也由在2000年五月公布的RFC 2818正式确定下来。简单来说,HTTPS就是安全版的HTTP,并且由于当今时代对安全性要求更高,chrome和firefox都大力支持网站使用HTTPS,苹果也在ios 10系统中强制app使用HTTPS来传输数据,由此可见HTTPS势在必行。

  3. HTTPS与HTTP的一些区别
    HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。
    HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。
    HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
    HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。

  4. HTTPS改造
    如果一个网站要全站由HTTP替换成HTTPS,可能需要关注以下几点:
    安装CA证书,一般的证书都是需要收费的,这边推荐一个比较好的购买证书网站:1)Let's Encrypt,免费,快捷,支持多域名(不是通配符),三条命令即时签署+导出证书。缺点是暂时只有三个月有效期,到期需续签。2Comodo PositiveSSL,收费,但是比较稳定。
    在购买证书之后,在证书提供的网站上配置自己的域名,将证书下载下来之后,配置自己的web服务器,同时进行代码改造。
    HTTPS 降低用户访问速度。SSL握手,HTTPS 对速度会有一定程度的降低,但是只要经过合理优化和部署,HTTPS 对速度的影响完全可以接受。在很多场景下,HTTPS 速度完全不逊于 HTTP,如果使用 SPDY,HTTPS 的速度甚至还要比 HTTP 快。
    相对于HTTPS降低访问速度,其实更需要关心的是服务器端的CPU压力,HTTPS中大量的密钥算法计算,会消耗大量的CPU资源,只有足够的优化,HTTPS 的机器成本才不会明显增加。
    推荐一则淘宝网改造HTTPS的文章。

  5. 使用SPDY加快你的网站速度
    2012年google如一声惊雷提出了SPDY的方案,大家才开始从正面看待和解决老版本HTTP协议本身的问题,SPDY可以说是综合了HTTPS和HTTP两者有点于一体的传输协议,主要解决:
    降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率。
    请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容。
    header压缩。前面提到HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量。
    基于HTTPS的加密协议传输,大大提高了传输数据的可靠性。
    服务端推送(server push),采用了SPDY的网页,例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。SPDY构成图:

SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTTP协议(将HTTP1.x的内容封装成一种新的frame格式),同时可以使用已有的SSL功能。

兼容性:

  1. HTTP2.0的前世今生
    顾名思义有了HTTP1.x,那么HTTP2.0也就顺理成章的出现了。HTTP2.0可以说是SPDY的升级版(其实原本也是基于SPDY设计的),但是,HTTP2.0 跟 SPDY 仍有不同的地方,主要是以下两点:
    HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
    HTTP2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATE

  2. HTTP2.0的新特性
    新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
    多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。多路复用原理图:

header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。目前,有大多数网站已经启用HTTP2.0,例如YouTuBe,淘宝网等网站,利用chrome控制台可以查看是否启用H2:

更多关于HTTP2的问题可以参考:HTTP2奇妙日常,以及HTTP2.0的官方网站。
关于HTTP2和HTTP1.x的区别大致可以看下图:

  1. HTTP2.0的升级改造
    对比HTTPS的升级改造,HTTP2.0或许会稍微简单一些,你可能需要关注以下问题:
    前文说了HTTP2.0其实可以支持非HTTPS的,但是现在主流的浏览器像chrome,firefox表示还是只支持基于 TLS 部署的HTTP2.0协议,所以要想升级成HTTP2.0还是先升级HTTPS为好。
    当你的网站已经升级HTTPS之后,那么升级HTTP2.0就简单很多,如果你使用NGINX,只要在配置文件中启动相应的协议就可以了,可以参考NGINX白皮书,NGINX配置HTTP2.0官方指南。
    使用了HTTP2.0那么,原本的HTTP1.x怎么办,这个问题其实不用担心,HTTP2.0完全兼容HTTP1.x的语义,对于不支持HTTP2.0的浏览器,NGINX会自动向下兼容的。

后记
以上就是关于HTTP,HTTP2.0,SPDY,HTTPS的一些基本理论,有些内容没有深入讲解,大家可以跟进参考连接具体查看。
关于HTTP1.x的一些优化方式,例如文件合并压缩,资源cdn,js,css优化等等同样使用与HTTP2.0和HTTPS,所以web前端的优化,还是要继续进行。
其实WEB发展如此迅速的今天,有些技术是真的要与时俱进的,就像苹果宣布ios 10必须使用HTTPS开始,关于web协议革新就已经开始了,为了更好的性能,更优越的方式,现在就开始升级改造吧

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.