Hello World

seajs入门

seajs是一个功能强大、使用简单的js包管理器,有了它,就可以方便快捷的实现js的模块化编程
下面简单介绍seajs的使用方法
比如有index.html,加入如下代码即可引入seajs及相关的配置:

<script src="assets/js/sea.js"></script>
<script>
  seajs.config({
    alias: {
      // 指定使用的 jQuery 版本 注意:这里的jauery是经过seajs包装的
      'jquery': 'jquery-1.8.0' 
    }
  })
  // 加载入口模块,main.js
  seajs.use('./assets/js/main')
</script>

在main.js内:

  define(function(require){
    var SS = require('./ss');
    var s = new SS();
    s.fun1(); //"fun1"
  });

上面通过require引入了一个叫做ss的模块,如下:ss.js

define(function(require, exports, module){
  //引入jquery模块
  var $ = require('jquery');
  //对外输入SS类
  module.exports = function(){
    function SS(){
 
    }
    SS.prototype = {
      fun1 : function(){
        alert('fun1');
      }
    };
    return SS;
  }();
});

关于seajs的简单使用,上面就讲完了,最后在说一个小细节:通过require(‘xxx’)引入的对象是“单例”的,比如下面s2.js

define(function(require, exports, module){
  module.exports = function(){
    function SS(){
      this.s = 1;
    }
    SS.prototype = {
      fun1 : function(){
        alert(this.s);
      }
    };
    //返回实例化后的SS
    return new SS();
  }();
});

再这样调用s2

  var t1 = require('./s2');
  t1.s = 3;
  var t2 = require('./s2');
  t2.fun1(); //这是,会alert 3

看到了吧,s2本来有一个属性s=1,再第一次引用t1内,被设置为3,再次通过require引用s2到t2,此时t2的s属性也变成了3,也就是说在seajs内有一个“require”的对象库,每一个通过module.exports输出的对象都会被保存在里面,再次require时,返回到仍然是原对象
————-
关于seajs更多内容,请参考:https://github.com/seajs/seajs/issues/266

Hello World

[转]PHP大小写是否敏感问题的汇总

PHP对大小写敏感问题的处理比较乱,写代码时可能偶尔出问题,所以这里总结一下。
但我不是鼓励大家去用这些规则。 推荐大家始终坚持“大小写敏感”,遵循统一的代码规范。

一、大小写敏感
1. 变量名区分大小写
所有变量均区分大小写,包括普通变量以及$_GET,$_POST,$_REQUEST,$_COOKIE,$_SESSION,$GLOBALS,$_SERVER,$_FILES,$_ENV 等;

2. 常量名默认区分大小写,通常都写为大写
(但没找到能改变这个默认的配置项,求解)

3. php.ini配置项指令区分大小写
如 file_uploads = 1 不能写成 File_uploads = 1

二、大小写不敏感
1. 函数名、方法名、类名 不区分大小写,但推荐使用与定义时相同的名字

function show(){   
    echo "Hello World";   
}   
show(); //输出 Hello World    推荐写法   
SHOW(); //输出 Hello World

2. 魔术常量不区分大小写,推荐大写
包括:__LINE__、__FILE__、__DIR__、__FUNCTION__、__CLASS__、__METHOD__、 __NAMESPACE__。

echo __line__; //输出 2   
echo __LINE__; //输出 3

3. NULL、TRUE、FALSE不区分大小写

$a = null;   
$b = NULL;   
$c = true;   
$d = TRUE;   
$e = false;   
$f = FALSE;   
var_dump($a == $b); //输出 boolean true   
var_dump($c == $d); //输出 boolean true   
var_dump($e == $f); //输出 boolean true

4.类型强制转换,不区分大小写
包括
•(int),(integer) – 转换成整型
•(bool),(boolean) – 转换成布尔型
•(float),(double),(real) – 转换成浮点型
•(string) – 转换成字符串
•(array) – 转换成数组
•(object) – 转换成对象

$a=1;   
var_dump($a); //输出 int 1   
$b=(STRING)$a;   
var_dump($b); //输出string '1' (length=1)   
$c=(string)$a;   
var_dump($c); //输出string '1' (length=1)

————–
参考:http://hi.baidu.com/wbkys/item/c6cf7b0c334700ca90571805

Hello World 业界杂谈 他山石

[转]来自浏览器创造者的web优化技巧

Jatinder Mann是微软Internet Explorer产品的一名项目经理,在BUILD 2012大会上,他做了题为“提高HTML5应用和网站性能的50条秘技(50 performance tricks to make your HTML5 apps and sites faster)”的演讲,介绍了很多为Web应用提速的技巧。

Mann的建议是按照下面六个原则组织的。

1. 快速响应网络请求

  • 避免重定向。排名前1000的网站中,63%使用了重定向。如果不执行重定向的话,页面速度可以提高10%。
  • 避免Meta-refresh。世界上14%的URL使用了Meta-refresh。
  • 尽可能通过CDN定位用户,使服务器响应时间最小化。
  • 从不同的域下载资源,使并发连接的应用最大化。
  • 复用连接。不要在响应请求时关闭连接。
  • 确保页面加载不会因合作伙伴网站提供的数据而延迟。
  • 了解耗时的网络组件,如重定向、缓存、DNS、请求和响应等。在IE 9和10中可以使用Navigation Timing API来测量浏览器花在每个操作上的时间。

2. 最小化下载的字节数

  • 加载页面时,要尽量减少下载的数据量。平均而言,每个页面要下载的数据量达777KB,其中有474KB的图片、128KB的脚本和84KB的Flash。
  • 请求gzip压缩的内容。
  • 将资源保存在本地的包中,比如为Windows商店应用生成的包资源索引(Package Resource Index)文件。这样当需要这些资源时就可以很容易地获取到。
  • 使用HTML5 App Cache缓存动态资源。App Cache会只下载一次资源,从而避免多次网络行程。当应用的版本号发生变化时,它会自动重新下载相应资源。
  • 尽量在响应中使用“Expires”字段来提供可缓存的内容。
  • 通过设定请求的If-Modified-Since字段来使用条件请求。
  • 缓存数据请求,如HTTP、XML和JSON等,因为大约95-96%的请求不会整天变化。虽然这个想法很合理,但实际缓存接收到的请求的网站所占比重还不到1%。
  • 用大写将文件命名标准化。虽然服务器可能把Icon.jpg当作 icon.jpg,但是对于Web平台而言,它们是不同的资源,对应不同的网络请求。

3. 高效地组织标记

  • 对于IE而言,请使用最新的标记标准,因为它速度最快。IE 10也能识别早期的IE6-IE9标记风格,但是其速度不如新的标记风格。
  • 特定的业务Web应用可能需要强制IE运行于传统模式,请使用HTTP头字段“X-UA-Compatible: IE=EmulateIE7”来代替HTML标签 ,这样速度会快一些。
  • 为了平滑地渲染,样式表应该链接在页面顶部的<head>之中的<title>后面。
  • 绝对不要在页面底部链接样式表。否则加载页面时可能会出现闪烁。
  • 对于分层样式,不要使用“@import”,因为它是同步的,会阻塞CSS数据结构的创建和屏幕绘制。
  • 避免样式的嵌入和内联,因为它会强制浏览器在HTML和CSS解析器之间进行上下文切换。
  • 仅包含必要的样式。不要下载和解析用不到的样式。
  • 仅在页面底部链接JavaScript。这可以确保脚本执行时图片和CSS等资源已经加载,无需等待,也避免了上下文切换。
  • 不要在页面开头链接JavaScript。如果某些脚本必须在开始处加载的话,请使用“defer”属性。
  • 不要内联JavaScript,这样可以避免上下文切换。
  • 使用“async”属性加载JavaScript,这样整个脚本就可以异步加载和执行。
  • 避免冗余代码。世界上52%的网页包含100行甚至更多的冗余代码,比如一个JavaScript文件被链接了两次。
  • 将一个JS框架标准化,无论是jQuery,Dojo,Prototype.js还是其他框架。浏览器没有必要加载多个功能基本相同的框架。
  • 不要加载FB和Twitter等网站的脚本,只是看起来很酷而已,它们会争用资源。

4. 优化多媒体资源的使用

  • 图片是最常用的资源,每个页面平均会下载58张图片。
  • 尽量避免下载太多图片,根据页面加载时间将图片最大数量控制在20-30之间。
  • 使用Image Sprites将多个图片组合成一个。该技术可以减少网络连接数,也会减少下载的字节数并节省GPU处理周期。
  • 手动创建Image Sprites,因为工具创建的可能会留下较大的空洞,这会加大需要下载的数据量,也需要更多的GPU 处理周期。
  • 使用PNG格式的图片,该格式在下载大小、解码时间、兼容性和压缩率之间实现了完美的折中。JPEG格式可以用于照片。
  • 使用原始的图像分辨率,这样可以避免下载不必要的数据以及缩放所需的CPU 处理。
  • 尽可能使用CSS3 Gradient替代图片。
  • 尽可能使用CSS3 border radius替代图片。
  • 使用CSS3 Transform来创建移动、旋转和倾斜效果。
  • 对于小型的单个图片,可以使用Data URI。这样可以节省一张图片的下载量。
  • 避免复杂的SVG,因为它们会延长下载和处理时间。
  • 当包含HTML5时,指定一个预览图片。这样浏览器就不必下载整个视频文件来确定预览图片了。
  • 使用HTML5来代替Flash、Silverlight或QuickTime。HTML5速度更快,而且其他几种形式的运行时插件会消耗系统资源。
  • 主动地以异步方式下载富媒体资源并将其保存在App Cache中。

5. 编写快速的JavaScript

  • 在JavaScript中进行数学运算时尽量使用整型。JavaScript的浮点运算比相应的整型运算耗费的时间要多得多。在进行数学运算,特别是计算密集型运算时,请使用Math.floor和Math.ceil将浮点数转换为整型数。
  • 降低JavaScript代码量,这样不但可以减少下载的数据量,而且能够提供更好的运行时性能。
  • 按需初始化JS。当需要时动态加载JS。
  • 通过缓存变量(如document和body等)使DOM交互减到最少。
  • 使用内置的DOM代码,如element.firstChild或node.nextSibling等。这些代码都是高度优化的,相对于第三方库能提供更好的性能。
  • 访问大量DOM元素时,使用querySelectorAll。
  • 使用.innerHTML来构建动态页面。
  • 批量标记更改。
  • 维护小巧而健壮的DOM——将其元素数目控制在1000以内。.
  • JSON快于XML。
  • 使用浏览器的JSON原生方法。
  • 不要滥用正则表达式。

6. 知道你的应用在做什么

  • 理解JavaScript定时器,了解setTimeout和clearInterval。除非确定要使用定时器完成一些功能,否则不要启动定时器。组合定时器也是如此。
  • 如果监视器的刷新率是60Hz,请将显式帧的定时器调整为16.7 ms。
  • 在IE 10、Chrome和Firefox中,图形处理请使用requestAnimationFrame动画函数。其绘制通过回调实现,因此不需要定时器。
  • 使用可见性API(document.hidden和 Visibilityhange)来确定应用程序的可见状态,如果页面是隐藏的,就关闭该活动。这样可以节省CPU和电池寿命。
  • Mann建议在IE中使用Windows Performance Tools来测试Web页面的性能, 并以减少CPU时间和增加并发性为目标进行优化

—————-
转自:http://www.searchsoa.com.cn/showcontent_68434.htm

Hello World 他山石

[转]CSS的position:fixed的使用

接触CSS已有相当长一段时间,我们竟然没有留意position:fixed的用法。

我们都知道CSS中定位属性position的值,除了默认的值外,还有absolute,relative和fixed。我平时比较常用absolute和relative,而position:fixed却没多关注。或许是因为当初在CSS中文手册中看到position:fixed旁边有说明“IE5.5及NS6尚不支持此属性”吧。

前段时间,在做一个项目时需要使一个层相对于浏览器边框固定,那时用position:absolute试了下,发觉absolute是对网页边框而言的。后来,上网查了一些根据滚动条的移动,动态地改变left和top的值的JavaScript语句,虽然能实现了类似的效果,但滚动条移动时,那个层晃来晃去的,感觉不好看,想要一种能使层固定不动的做法。

且看下面的代码:

#help{
    width:30px;
    height:20px;
    background-color:green;
    position:fixed;
    left:60px;
    top:100px;
}

我们用上面这段代码来定义页面上的一个层“help”(id=“help”)。这样就能实现我们想要的效果了。

在IE8、Firefox、Opera、Google等浏览器中测试,都没有问题,唯独低版本的IE中,这个属性无效。

转自:http://www.blogjava.net/rongxh7/archive/2009/11/22/303225.html

——————-
ps:置顶菜单的定位:

div.top_nav{
    width:100%;
    position:fixed;
    left:0;
    top:0;
    ......
}

Hello World

使用jsp输出js

    服务器端将多个js文件整合到一个请求内输出,是一种常用的减少页面请求数的方法;另外,如果js文件间的逻辑是相互依赖的,需要控制它们的加载顺序,在服务器端做文件整合也可以规避这种扯淡的事
    以前php的时候,使用简单的“include”,就可以完成这事,最近做了一个java的项目,也想使用这种方式,就想用jsp的include试试,已经想到了可能会有中文乱码的问题,于是加上了:pageEncoding=”UTF-8″

  <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
  <%@ page contentType="application/javascript" %>
  <%@ include file="xxx1.js" %>
  <%@ include file="xxx2.js" %>
  <%@ include file="xxx3.js" %>

    结果~~~还乱码 o(╯□╰)o
    查了很多资料,做了N多测试,大概有这么几种
  1. 将文件另存为“utf-8”格式,覆盖到项目里
    —-觉得这是多此一举,直接在eclipse里修改文件属性不就行了:检查文件属性,本来就是utf-8
  2. 在web.xml里做一些设置,让js文件可以做jsp解析
    —-无效
  3. script标签加上“charset”,等效的,jquery ajax加载脚本上,加上“scriptCharset”
    —-折腾了半天,还是无效
  4. 。。。。。
    搞来搞去,还是没有成功,jsp的乱码问题果然名不虚传 +_+

    刚才,本着死马当活马医的精神,拿第一种方法试了试,使用txt编辑器将js代码拷出来,另存为utf-8格式,在覆盖到项目目录内~~ok了 ~\(≧▽≦)/~
    (看来eclipse文件属性里的编码只是说以什么编码方式打开,和文件本身的编码的方式无关)

—————
    btw:两年多没有碰过java了,这次帮别的部门做项目,不得不又开始搞jsp,有了php的类比,深感用jsp/tomcat做web网站有多么麻烦,jstl简直多此一举。更喜欢php的方式:通过“”标签将服务器端逻辑和浏览器端的逻辑完全分开,简洁而不失优雅。反观el/jstl,非要将本来属于java端的逻辑与html混在一起~~~java还是适合做复杂的后台逻辑,然后用类似thrift的东西与web端解耦

Hello World 业界杂谈

微信二维码登录功能的实现

微信的二维码登录是个很酷的功能:客户端登录后,通过扫描web端的二维码,实现统一用在web端的登录

下面要讲的就是如何这一个功能:

实现思路:
web:访问web页面时,如果未登陆,会根据随机字符串生成二维码,显示到页面上,同时将该字符串保存起来,然后页面会通过ajax轮询方式访问web端,查看这个二维码是否可以登录,如果可以登录,则将用户名/id写入session,然后将页面跳转到登录状态
客户端:扫描页面的二维码后,通过http方式访问web端,将二维码源字符串,以及客户端登录用户的用户名/id发送过去,告诉web端这个用户可以登录

思路很简单,实现起来也不麻烦,下面会列一些核心代码,详细的,请看这个demo,代码可以到这里下载

web端:
二维码生成,使用qrcode库:

/*
 * $source  二维码源字符串
 * $temp  图片缓存目录
 * $errorCorrectionLevel, $matrixPointSize 错误等级、图片大小
 */
function getQRCodeImg($source, $temp, $errorCorrectionLevel, $matrixPointSize){
	$date = date('Ymd');
	$PNG_WEB_DIR = DIRECTORY_SEPARATOR.$temp.DIRECTORY_SEPARATOR.$date.DIRECTORY_SEPARATOR;
	$PNG_TEMP_DIR = dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'webroot'.DIRECTORY_SEPARATOR.$temp.DIRECTORY_SEPARATOR.$date.DIRECTORY_SEPARATOR;
	include_once '../lib/qrcode/qrlib.php';
	if (!file_exists($PNG_TEMP_DIR))
		mkdir($PNG_TEMP_DIR);
 
	if (!in_array($errorCorrectionLevel, array('L','M','Q','H')))
		$errorCorrectionLevel = 'M';    
 
	$matrixPointSize = min(max((int)$matrixPointSize, 1), 10);
 
	$filename = md5($source.'|'.$errorCorrectionLevel.'|'.$matrixPointSize).'.png';
 
	QRcode::png($source, $PNG_TEMP_DIR.$filename, $errorCorrectionLevel, $matrixPointSize, 2); 
 
	return $PNG_WEB_DIR.$filename;
}

页面的ajax相当简单:

//ajax轮询时间间隔,通过服务器配置得到
var ASK_TIME = <?php echo conf('ajax.time');?>;
var i = 0;
$(function(){
	setInterval(function(){
		$.get('page_ask.php?source=<?php echo $source;?>', function(d){
			//登录成功,跳转页面
			if(d && d.login){
				window.location = '';
			}
			//测试信息的显示
			$('div.test').html(i++ + '---' + JSON.stringify(d));
		}, 'json');
	}, ASK_TIME * 1000);
});

page_ask.php的核心代码,服务端存储使用mongodb

//ajax轮询检查是否登录		登录成功返回user,否则为false 
function checkSource($source){
	$collection = getDBCollection(conf('mongo.collection'));
	$sou = $collection->findone(array('_id' => $source));
	if($sou && $sou['login'] === 1){
		return $sou['user'];
	}
	return false;
}

客户端通过访问“/client_login.php?source=xxx&user=张三”实现登录,当然,实际的系统应该是传userId

//取记录  返回  1 登录成功  10 source有误  11 此source已使用过  21 无此user(暂无此项)
function checkSource4Login($source, $user){
	$collection = getDBCollection(conf('mongo.collection'));
	$sou = $collection->findone(array('_id' => $source));
	$r = 10;
	if($sou && isset($sou['login'])){
		if($sou['login'] === 0){
			$r = 1;//校验正确
		}else{
			$r = 11;
		}
	}
	//保存登录状态
	if($r == 1){
		//保存登录状态
		$collection->update(
			array('_id' => $source), 
			array('$set' => array('login' => 1, 'user' => $user)), 
			array('safe'=>true)
		);
	}
	return $r;
}

下面是客户端的几个片段,这里使用的是ios
二维码扫描使用的库是ZBar,注意,需要添加一坨frameworks

-(IBAction)startScan:(id)sender
{
    ZBarReaderViewController *reader = [ZBarReaderViewController new];
    reader.readerDelegate = self;
    ZBarImageScanner *scanner = reader.scanner;
    [scanner setSymbology:ZBAR_I25 config:ZBAR_CFG_ENABLE to:0];
    [self presentModalViewController:reader animated:YES];
    [reader release];
}
//将上面这个方法注册给“开始扫描”按钮:
[QRBut addTarget:self action:@selector(startScan:) forControlEvents:UIControlEventTouchUpInside];
//扫描成功后,会自动调用此方法
- (void) imagePickerController: (UIImagePickerController*) picker didFinishPickingMediaWithInfo: (NSDictionary*) info
{
    UIImage *image = [info objectForKey: UIImagePickerControllerOriginalImage];    
    id<NSFastEnumeration> results = [info objectForKey: ZBarReaderControllerResults];
    ZBarSymbol *symbol = nil;    
    for(symbol in results){
        break;
    }    
    if(!symbol || !image){
        return;
    }    
    NSLog(@"symbol.data = %@", symbol.data);  
    [self sendRQcode:symbol.data];  
    [picker dismissModalViewControllerAnimated: YES];
} //imagePickerController

//取得二维码后,通过http方式发送,然后解析返回的json数据

- (void) sendRQcode:(NSString*)code
{
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    NSDate* dat = [NSDate dateWithTimeIntervalSinceNow:0];
    NSTimeInterval a=[dat timeIntervalSince1970];
    NSString *timeString = [NSString stringWithFormat:@"%f", a];
    NSHTTPURLResponse* urlResponse = nil;
    NSError *error = [[NSError alloc] init];
    NSLog(@"-------loginClick------user:%@", timeString);
    NSString *url = [[[[[@"http://xxxxxx/client_login.php?user=" stringByAppendingString:user]                     
            stringByAppendingString:@"&source="] stringByAppendingString:code]
            stringByAppendingString:@"&time="] stringByAppendingString:timeString];
    [request setURL:[NSURL URLWithString:url]];
    [request setHTTPMethod:@"GET"];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];  
    //发送请求  
    NSData *returnData = [NSURLConnection sendSynchronousRequest:request 
            returningResponse:&urlResponse error:&error];
    NSLog(@"-------sendRQcode--status:------%d", [urlResponse statusCode]);
    NSString *login = @"0";
    if(returnData != NULL && [urlResponse statusCode] == 200) {
        //解析json
        NSDictionary *data = [returnData objectFromJSONData];
        NSLog(@"-------sendRQcode----returnData:------%@", data);
        login = [data objectForKey:@"login"];
    }
    NSLog(@"-------sendRQcode----login:------%@", login);
    msg = @"";
    if([login isEqual: @"1"]){
        msg = @"登录成功!";
    }else if([login isEqual:@"0"]){
        msg = @"网络连接错误,请检查网络设置后重试!";
    }else{
        msg = [[@"未知错误(login:" stringByAppendingString:login] stringByAppendingString:@"),请检查网络设置后重试!"];
    }
    NSLog(@"-------sendRQcode----msg:------%@", msg);
    [self alertWithMessage:msg];
}//sendRQcode

———–
代码就展示这么多,具体的,请访问github:https://github.com/jklgithub/qrcode_login
本文为本人原创,转载请注明地址:http://www.jiangkl.com/2012/10/%E4%BA%8C%E7%BB%B4%E7%A0%81%E7%99%BB%E5%BD%95_qrcode_zbar/
演示地址:dev.jiangkl.com (mongo不太稳定,测试不通的话可以给我留言)

Hello World

html5 localStorage使用备忘

localStorage和sessionStorage是html5提出的web存储方案,难能可贵的是,除了chrome/safari/firefox/opera,老大难的IE,从IE8便开始支持它们!所以,今天好好研究了这两个东西:

1. localStorage地持久的,sessionStorage是只对当前页面,也就是“session”的,关闭浏览器、关闭窗口就会失效,甚至在同一个浏览器中再开一个窗口,都不能相互访问。除此之外,二者的表现是一样的

2. 存储容量的大小:html5标准是5M,但具体的,也依照各个浏览器的实际情况(这一点要多加注意)

3. api

window.localStorage.name = 'rainman';           // 赋值
window.localStorage.setItem('name','cnblogs');  // 赋值
window.localStorage.getItem('name');            // 取值
window.localStorage.removeItem('name');         // 移除值
delete window.localStorage.name                 // 移除值
window.localStorage.clear();                    // 删除所有localStorage

4. 在将value付给localStorage时候,会自然的调一下value.toString(),如果value是一个对象,而没有做变换,保存的就是字符串“[object Object]”~~汗吧。所以这里需要用到JSON.stringify()和JSON.parse(),万幸的是支持localStorage,都支持这两个方法(包括IE8)

BTW:其他web存储方式:
Database Storage是html5的“Web SQL Database”API,可以想操作其他关系型数据库一样,使用sql读写~~可惜的是,只有Chrome支持它
————

Hello World

html5研究—-自动化的manifest

manifest是html5提供的离线web应用解决方案,它的作用有两个:
1. 连网情况下,访问使用manifest的页面时(之前曾经访问过),会先加载一个manifest文件,如果这个manifest文件没有改变,页面相关的资源便都来自浏览器的离线缓存,不会再有额为的网络请求,从而大大提高页面相应时间
2. 断网时,在浏览器地址栏输入页面url,仍然能够正常显示页面,以及正常使用不依赖ajax的功能

之前曾想在手机webview的开发中使用这种功能,但权衡再三,也没有使用,其中一个原因,便是manifest配置太繁琐,很不利于开发和维护,比如,一个正常的manifest文件是这样的:

test.manifest

CACHE MANIFEST
 
# VERSION 0.3
 
# 直接缓存的文件
CACHE:
/index.html
/img/x1.png
/img/x2.png
/img/x3.png
/js/xx.js
/css/xx.css
 
# 需要在线访问的文件
NETWORK:
 
# 替代方案
FALLBACK:

也就是说,每一个要缓存的文件都要配置到“CACHE”下面,每次修改这些文件后,都要修改VERSION~~

但是,离线缓存这个功能又是如此的诱人,所以就想着用其他方式绕开繁琐的配置

首先,test.manifest文件的扩展名必须是manifest静态文件,不能动态声称,于是便想使用php来生成manifest文件,对于apache服务器,再网站根目录下添加.htaccess文件,将/xxx.manifest请求,转给/manifest/xxx.php:

.htaccess

RewriteEngine on
RewriteRule ^([a-zA-Z0-9_\-]{1,})\.manifest$ manifest/$1.php

然后,我们要寻找一个可以自动更新的VERSION,我这里使用的时文件更新时间,对,寻找所有缓存文件中最晚修改过的那个文件的更新时间,然后将可能需要缓存的文件列表打印出来

index.html

<!DOCTYPE HTML>
<html manifest="/test.manifest">
    <head>
        <meta charset="UTF-8">
        <title>manifest测试</title>
        <link rel='stylesheet' href='/css/test.css' />
        <link rel='stylesheet' href='/css/test2.css' />
        <script type='text/javascript' src='/js/test.js'></script>
    </head>
<body>
    <img src="/img/test-pass-icon.png"></img>
    <hr>
    <img src="/img/btn_s.png"></img>
    <hr>
    <img src="/img/charts_images/dragIcon.gif"></img>
    <hr>
    <img src="/img/xx.png"></img>
    <hr>
</body>
</html>

/test.manifest将会转到/manifest/test.php:

/manifest/test.php

<?php 
    require_once 'manifestUtil.php';
    //配置信息
    $webroot = '../';
    $urlroot = '';
    //需要缓存的目录和文件(目录内的所有文件都将被输出)
    $sources = array(
        'img',
        'js/test.js',
        'css',
    );
 
    $updateTime = 0;
    $files = listFiles($sources, $updateTime, $webroot, $urlroot);
?>
CACHE MANIFEST 
# version <?php echo $updateTime;?>
 
CACHE:
/index.html
<?php echoFiles($files);?>
 
NETWORK:
*
 
FALLBACK:

manifestUtil.php

<?php 
//更具配置信息,读取文件列表
function listFiles($sources, &$updateTime, $webroot = '../', $url = ''){
    $r = array();
    foreach($sources as $s){
        $r = array_merge($r, getFiles($webroot.'/'.$s, $updateTime, $url.'/'.$s));
    }
    return $r;
}
//读取文件列表
//$types,需要缓存的文件类型
function getFiles($fileName, &$updateTime = 0, $url = '', $levels = 100, 
        $types = array('jpg', 'png', 'gif', 'jpeg', 'css', 'js')){
    if(empty($fileName) || !$levels){
        return false;
    }
    $files = array();
    if(is_file($fileName)){
        $updateTime = getMax(filectime($fileName), $updateTime);
        $files[] = $url;
    }else if($dir = @opendir($fileName)){
        while(($file = readdir($dir)) !== false){
            if(in_array($file, array('.', '..')))
                continue;
            if(is_dir($fileName.'/'.$file)){
                $files2 = getFiles($fileName.'/'.$file, $updateTime, $url.'/'.$file, $levels - 1);
                if($files2){
                    $files = array_merge($files, $files2);
                }
            }else{
                $updateTime = getMax(filectime($fileName.'/'.$file), $updateTime);
                $type = end(explode(".", $file));
                if(in_array($type, $types)){
                    $files[] = $url.'/'.$file;
                }
            }
        }
    }
    @closedir($dir);
//    echo date("Y-m-d H:i:s",$updateTime).'<hr>';
    return $files;
}
//打印缓存文件列表
function echoFiles($files){
        for($i = 0; $i < count($files); $i++){
            echo $files[$i].'
';//后面加一个换行
        }
}
//取较大的更新时间
function getMax($a, $b){
    return $a < $b ? $b : $a;
}

over~~~就是这些,下面的可能出现的问题:
这个只是初级方案,对于动态网页还有问题,比如,你请求不是静态的html,而是动态的php,而这个php里有require了其他的php,如果这些php发生了修改,上面的方案时不知道的,生成的manifest不会发生变化,这里就需要对方案做一个升级:除$sources之外,再加一个,比如$aboutSources,来配置其他相关文件,拿$aboutSources再调一次listFiles,综合$sources与$aboutSources的updateTime生成VERSION值
另外一个问题是,遍历相关文件,需要多次磁盘IO,如果文件太多,也会消耗较多的时间~~~凡事有利必有弊,如果对此不满,可以手动配置一个VERSION,每次修改后,更新VERSION值
—————
本文为本人原创,转载请注明出处
github:https://github.com/jklgithub/auto_manifest.git
参考:青梅煮酒-HTML5 离线存储实战之manifest

Hello World

node.js入门—-静态文件服务器

本文展示是基于node.js的静态文件服务器,代码参考自这里,主要是练习node http、文件模块的使用,另外,对理解http协议也很有帮助
除了实现了基本的路由控制,还实现了MIME类型、304缓存、gzip压缩、目录读取

首先是配置文件,setting.js

var setting = {
  webroot : '/xxx/xxx/webroot',
  viewdir : false,
  index : 'index.html',	//只有当viewdir为false时,此设置才有用
  expires : {
    filematch : /^(gif|png|jpg|js|css)$/ig,
    maxAge: 60 * 60 //默认为一个月
  },
  compress : {
    match : /css|js|html/ig
  }
};
module.exports = setting;

MIME映射,mime.js

var mime = {
  "html": "text/html",
  "ico": "image/x-icon",
  "css": "text/css",
  "gif": "image/gif",
  "jpeg": "image/jpeg",
  "jpg": "image/jpeg",
  "js": "text/javascript",
  "json": "application/json",
  "pdf": "application/pdf",
  "png": "image/png",
  "svg": "image/svg+xml",
  "swf": "application/x-shockwave-flash",
  "tiff": "image/tiff",
  "txt": "text/plain",
  "wav": "audio/x-wav",
  "wma": "audio/x-ms-wma",
  "wmv": "video/x-ms-wmv",
  "xml": "text/xml"
};
module.exports = mime;

然后是主程序,server.js

var http = require('http');
var setting = require('./setting.js');
var mime = require('./mime');
var url = require('url');
var util = require('util');
var path = require('path');
var fs = require('fs');
var zlib = require('zlib');
//访问统计
var number = 0;
var accesses = {};
 
http.createServer(function(req, res){
  res.number = ++number;
  accesses[res.number] = {startTime : new Date().getTime()};
  var pathname = url.parse(req.url).pathname;
  //安全问题,禁止父路径
  pathname = pathname.replace(/\.\./g, '');
  var realPath = setting.webroot + pathname;
  accesses[res.number].path = pathname;
  readPath(req, res, realPath, pathname);
}).listen(8000);
 
console.log('http server start at parth 8000\n\n\n');
//判断文件是否存在
function readPath(req, res, realPath, pathname){
  //首先判断所请求的资源是否存在
  path.exists(realPath, function(ex){
    console.log('path.exists--%s', ex);
    if(!ex){
      responseWrite(res, 404, {'Content-Type' : 'text/plain'}, 
          'This request URL ' + pathname + ' was not found on this server.');
    }else{
      //文件类型
      fs.stat(realPath, function(err, stat){
        if(err){
          responseWrite(res, 500, err);
        }else{
          //目录
          if(stat.isDirectory()){
            //是否读取目录
            if(setting.viewdir){
              fs.readdir(realPath, function(err, files){
                if(err){
                  responseWrite(res, 500, err);
                }else{
                  var htm = '<html><head><title>' + pathname + '</title></head><body>' + pathname + '<hr>';
                  for(var i = 0; i < files.length; i++){
                    htm += '<br><a href="' + pathname + (pathname.slice(-1) != '/' ? '/' : '') 
                        + files[i] + '">' + files[i] + '</a>', 'utf8';
                  }
                  responseWrite(res, 200, {'Content-Type' : 'text/html'}, htm);
                }
              });
            }else if(setting.index && realPath.indexOf(setting.index) < 0){
              readPath(req, res, path.join(realPath, '/', setting.index), path.join(pathname, '/', setting.index));
            }else{
              responseWrite(res, 404, {'Content-Type' : 'text/plain'}, 
                  'This request URL ' + pathname + ' was not found on this server.');
            }
          }else{
            var type = path.extname(realPath);
            type = type ? type.slice(1) : 'nuknown';
            var header = {'Content-Type' : mime[type] || 'text/plain'};
            //缓存支持
            if(setting.expires && setting.expires.filematch 
                && type.match(setting.expires.filematch)){
              var expires = new Date(),
              maxAge = setting.expires.maxAge || 3600 * 30;
              expires.setTime(expires.getTime() + maxAge * 1000);
              header['Expires'] = expires.toUTCString();
              header['Cache-Control'] = 'max-age=' + maxAge;
              var lastModified = stat.mtime.toUTCString();
              header['Last-Modified'] = lastModified;
              //判断是否304
              if(req.headers['if-modified-since'] && lastModified == req.headers['if-modified-since']){
                responseWrite(res, 304, 'Not Modified');
              }else{
                readFile(req, res, realPath, header, type);
              }
            }else{
              readFile(req, res, realPath, header, type);
            }
          }
        }
      });
    }
  });
}
//读文件/压缩/输出
function readFile(req, res, realPath, header, type){
  var raw = fs.createReadStream(realPath), cFun;
  //是否gzip
  if(setting.compress && setting.compress.match 
      && type.match(setting.compress.match) && req.headers['accept-encoding']){
    if(req.headers['accept-encoding'].match(/\bgzip\b/)){
      header['Content-Encoding'] = 'gzip';
      cFun = 'createGzip';
    }else if(req.headers['accept-encoding'].match(/\bdeflate\b/)){
      header['Content-Encoding'] = 'deflate';
      cFun = 'createDeflate';
    }
  }
  res.writeHead(200, header);
  if(cFun){
    raw.pipe(zlib[cFun]()).pipe(res);
  }else{
    raw.pipe(res);
  }
}
//普通输出
function responseWrite(res, starus, header, output, encoding){
  encoding = encoding || 'utf8';
  res.writeHead(starus, header);
  if(output){
    res.write(output, encoding);
  }
  res.end();
  accesses[res.number].endTime = new Date().getTime();
  //日志输出
  console.log('access[%s]--%s--%s--%s--%s\n\n', res.number, accesses[res.number].path, 
      (accesses[res.number].endTime - accesses[res.number].startTime), 
      starus, (output ? output.length : 0));
 delete accesses[res.number];
}

over!
尚欠缺的功能:日志记录、断点、容错等~~以后有时间再加啦

Hello World

node.js入门—-做个代理服务器

看到node.js的httpServer和http.request,第一个想法居然是可以用它做一个代理服务器
下面代码,实现了代理的基本功能,通过网络的代理设置将你的浏览器的请求转到这个httpServer上,其接收到浏览器的http请求,转发到目的服务器,再将收到的数据转移到浏览器~~~就一二道贩子。

var _http = require('http'),
    _util = require('util'),
    //记录当前是第几个请求
    number = 0;
 
_http.createServer(function(req, res){
	number++;
	res.number = number;
	var headers = req.headers,
		post = '',
		url = req.url;	//_url.parse(req.url, true)--解析url
	console.log('\n======\n[' + number + ']--http--start--[' + req.method + ']' + url);
	if(req.method == 'POST'){
		//接收post
		req.on('data', function(chunk){
			post += chunk;
		});
		req.on('end', function(){
			post = _querystring.parse(post);
			httpRequest(res, headers, url, post);
		});
	}else{
		httpRequest(res, headers, url);
	}
}).listen(3333);
 
console.log('http server start at parth 3333\n\n\n');
 
/*
 * 发送请求
 */
function httpRequest(mainRes, headers, url, post){
	var host = headers.host;
	host = host.split(':');
	var options = {
		host : host[0],
		port : (host[1] ? host[1] : '80'),
		path : url,
		method : (post ? 'POST' : 'GET'),
		headers : headers
	};
	//去掉gzip
	delete options.headers['accept-encoding'];
	console.log('[' + mainRes.number + ']----start http.request-- options:\n%s\n', _util.inspect(options));
 
	var req_ = _http.request(options, function(res_){
		var status_ = res_.statusCode,
			headers_ = res_.headers,
			data = '';
		console.log('[' + mainRes.number + ']----export http.request-(' + status_ + ')-headers:%s', 
				_util.inspect(headers_));
 
		//写header
		mainRes.writeHead(status_, headers_);
 
		var encoding = 'utf8';
		//根据数据类型确定编码方式
		if(headers_['content-type'] && (headers_['content-type'].indexOf('image') >= 0 
				|| headers_['content-type'].indexOf('application') >= 0 
				|| headers_['content-type'].indexOf('audio') >= 0 
				|| headers_['content-type'].indexOf('video') >= 0)){
			encoding = 'binary';
		}
		res_.setEncoding(encoding);
		//接收数据,转给原请求
		res_.on('data', function(d){
			console.log('data-------' + d.length);
			var b = new Buffer(d, encoding);
			mainRes.write(b, encoding);
		});
		res_.on('end', function(){
			//请求结束
			mainRes.end();
			console.log('[' + mainRes.number + ']--------http.request---end----');
		});
	});
	req_.on('error', function(e){
		console.log([' + mainRes.number + '] + '----ERROR----problem with request: ' + e.message);
	});
	//发送post
	if(post){
		req_.write(_querystring.stringify(post));
	}
	req_.end();
}

当然,这段代码仅仅是学习目的的,并没有什么实际意义,https、gzip的问题都没解决,如果网站是gbk的,拿它访问也会乱码
如果要拿它翻墙,需要部署两个类似的服务,比如墙外A墙内B,将浏览器代理到B,由B将host隐藏起来,再转到A,A将隐藏的目的host恢复过来,再去访问最终的目的网站
用上面的目的试了试非死不可,不稳定,时不时的会出错~~等完善的再分享给大家