Gone With the Wind

uplodify中的大坑

项目中有使用到需要上传图片的地方, 项目本身之前是由别的同事开发的, 上传使用了uploadify来上传图片, 我们这次准备重构, 因为时间较紧, 所以也就沿用的之间的上传方法, 第一次使用uploadify, 觉得用的人还挺多, 不会有什么问题, 但是问题接踵而来。

自动发起的http请求.

因为没有影响功能, 一开始没有特别关注, 后来看控制台, 发现老是会多发出去一个请求, URL是当前域名, 因为URL有问题, 所以老是404, 查遍了所有代码都没有找到发送这个请求的地方, 后来把目光锁定到uploadify上面去, 注释了uploadify, 就发现没有这个请求了, 搜了一下, 遇到这个问题的还挺多, 网上随便找个解决办法就行了, 具体就是当你没有设置image_url的时候, 也会发送一个请求去拿这张图片, 下面有个大坑。

flash版本不会带上二级域名的cookie.

说先说下业务场景, 我们使用oauth的方式登录, 域名是: a.test.com, 登录后, 登录系统会将cookie写入到test.com域名下去, 子系统访问的时候拿到test.com下的cookie, 到登录系统中获取用户信息, 完成用户认证流程, 一个很常规的登录流程.

那么问题来了, 我们子系统的域名是a.test.com, 上传功能我们并不想开放给所有用户使用, 会有一些acl鉴权, 所以需要检测登陆态, 在chromeie下一切正常, 当使用safarifirefox的时候, 后端会提示无法获取用户信息的错, 打印了一下后端拿到的headers, 发现只有a.test.com域名下的cookie, test.com域名下的cookie全都没有获取到, 导致拿不到用户信息. 到这里就很清晰了, 一开始我以为是flash的问题, 对flash也不是很熟, 但是想想flash应该不会这么坑, 就下载了一下编译器自己写了一个测试了一下, 果然在所有浏览器下都是正常的, 那么就只能确定是uploadify的问题了, 找了个反编译工具看了下uploadify的代码, 有点多, 反编译后也不大好看, 没有找到具体原因.

最后的解决办法是, 先将上传的鉴权去掉了, 赶项目时间点, 回头马上迁移到html5去.

如何爬带有reCAPTCHA的页面

reCAPTCHA简介

reCAPTCHA项目是由卡内基梅隆大学所发展的系统,主要目的是利用CAPTCHA技术来帮助典籍数字化的进行,这个项目将由书本扫描下来无法准确的被光学文字辨识技术(OCR, Optical Character Recognition)识别的文字显示在CAPTCHA问题中,让人类在回答CAPTCHA问题时用人脑加以识别[1]。reCAPTCHA正数字化《纽约时报》(New York Times)的扫描存文件,目前已经完成20年份的数据,并希望在2010年完成110年份的数据。2009年9月17日,Google宣布收购reCAPTCHA。这是一个伟大的项目,在发挥验证码作用的同时,使得输入验证码的人对全人类做出了贡献。

项目初衷

有江湖的地方就有爬虫,为什么我会去爬一个带有验证码的网站呢?前段时间刚买了车,但是运到店里面需要一些时间,从销售那问了车架号就一直想蠢蠢欲动,看到车托之家有人可以用车架号查询车辆生产以及配置信息,那也是一个别人开发的系统,但是不知道为什么最近一直处于瘫痪状态,好在还有英文网站可以查询。但是因为英文网站带有reCAPTCHA,而天朝又因为某些不可抗力因素导致reCAPTCHA无法使用,除非挂VPN,一般人查不了配置,那么,为什么不自己开发一个呢?

实现过程

其他爬页面的细节就不说了,主要还是说说reCAPTCHA如何爬,验证码也没想着自动识别,那太复杂了,就把验证码爬下来让用户自己输入吧,reCAPTCHA使用很简单,在页面上嵌入一段JS,然后去问谷歌这个验证码的输入正确与否就OK了,所有遇到的问题如下:

  • script标签发出的请求带有referer头,显然申请reCAPTCHA服务的时候会生成一个唯一性的token,带着token才能请求到验证码,而发送请求的时候会验证`referer头,如果不是127.0.0.1或者localhost或者申请时候填写的网址,那么谷歌就拒绝提供服务。解决办法:本身因为天朝地址无法访问谷歌服务,就准备在服务端对这些请求做一次转发,转发时候不要带上referer头即可。

  • reCAPTCHA的运行原理很简单,首先页面加载一个谷歌JS文件:http://www.google.com/recaptcha/api/challenge?k=6Ldlev8SAAAAAF4fPVvI5c4IPSfhuDZp6_HR-APV,这个k参数就是上面提到申请reCAPTCHA服务的时候给你一个唯一性的token,当然要经过服务器转发,不然天朝子民无法获得这个请求,这个JS的返回格式如下:

    1
    
    var RecaptchaState = {
        challenge : '03AHJ_VutQSbh3e1Zy9JyHmt_Zmt83Gl7r0UpTa7Hq8KHlT7XGb1zxpMDyON6W70ZB3K2rY1kxWj31xkzVCJEy4SI-KsxOac-Rvh32JMoIT2-IX6gbAgj24p-SiYSYhHyYu00OAcePod4nf5QZuFdOWNRwZfcCkemd55F6dfJOqDTmPp-i2ySI2lId8Eo5d557NGZvpYYBmLLwFtyewzBfhpEwYJgR2L0lSA',
        timeout : 1800,
        lang : 'zh-CN',
        server : 'https://www.google.com',
        site : '6Ldlev8SAAAAAF4fPVvI5c4IPSfhuDZp6_HR-APV',
        error_message : '',
        programming_error : '',
        is_incorrect : false,
        rtl : false,
        t1 : 'Ly93d3cuZ29vZ2xlLmNvbS9qcy90aC9TMFJ2cEhDbHY2a29udUt6cUtXYkxMUnY0WHM0bEVSblphTGlucDNfelo4Lmpz',
        t2 : '',
        t3 : 'OXVBWHdpOTZlcEp....(这里是一大串类似密文的东西)'
    };
    
    document.write('<scr'+'ipt type="text/javascript" s'+'rc="' + RecaptchaState.server + 'js/recaptcha.js"></scr'+'ipt>');

注意这个地方有个server属性,谷歌还是挺良心的,后续发的所有请求,比如获取验证码图片或者语音的请求,host都是这个server属性所带的值,同样,这些请求需要转发,不然请求不到,在服务端获取这个JS文件时,直接把server属性替换掉,替换你自己服务器转发地址,后续的请求就都往你自己服务器上发了,既能让天朝子民访问reCAPTCHA,又能在自己的域名上爬到别人域名的reCAPTCHA验证码。

  • 谷歌毕竟是谷歌,即使爬到了验证码,他也不会让你好用,谷歌的识别能力还是挺强的,原本想带上一些随机agentx-forwarded-for之类的头来模拟我是一个代理而已,但是无一例外,都被识别为了爬虫而拒绝服务,最后采用的还是没有带上任何头,直接请求,至少服务能用,但是等到用的人多了之后,验证码就会变得非常复杂,好在还有语音验证码可以用。

总结

这次爬得不算完美,当PV超过2 300的时候,验证码就变得非常恶心了,常人一般难以识别,这块还在想办法。最后贴出这个应用实例吧:宝马中文车架号查询系统

coffee在编写过程中的一些坑

CoffeeScript在编写过程之中,相对于JS可以带来很多便利性,但是同时也会引入一些问题,JS相对于Coffee来说有一些繁琐,但是相对严谨。

这里会列出coffee诸多的坑,其实也不能算是坑,只是在还没有灵活运用时,很难排查的一些小问题,也要注意一下自己平时的编写习惯。

  • 在生产过程中的一个案例。
1
{
  mode,
  base_cdn, 
  # data : encodeURIComponent JSON.stringify { formData : body, type },
  web_config: JSON.stringify web_config,
  user : locals.user
}

这个JSON原来的代码是:

1
{
  mode,
  base_cdn, 
  # data : encodeURIComponent JSON.stringify { formData : body, type },
  web_config: JSON.stringify web_config,
}

因为Coffee在描述JSON时候无需添加逗号,然后写这段代码的同事恰好将每一行后面都加上了逗号,然后我一看,没啥问题,紧接着加了一个属性”user”,乍一看,基于coffee松散的语法,毫无任何问题。然后,发现后面,这个JSON的user属性始终是undefined,因为后面有很长一段逻辑,一直以为是后面的逻辑出了问题,经过一个下午的调试后,才开始怀疑是JSON的问题,编译一看,顿时内牛满面:

1
{
  mode: mode,
  base_cdn: base_cdn,
  web_config: JSON.stringify(web_config, {
    user: locals.user
  }
}

node cover

nodejs 的三大特色:

nodejs

异步解决方案历史变迁

回调嵌套 -> event-pipe, async -> co -> fibjs

koa

基于co, 使用generator来使得异步代码使用起来同步化。

需求来了

  1. koa封装了大量逻辑, 只想使用类似connect的提供原始IncomingMessageServerResponse对象.

  2. 很多中间件都是采用connect中间件方式写的, 如果切换到koa, 必定要对原先的代码做修改.

node-cover

  1. 只提供基础的http server服务.

  2. 兼容connect中间件, 不兼容koa中间件, 但是可以使用ES6写的各种模块.

  3. 无缝切换现有项目, 同时使用co写同步代码.

使用方法

coffee-script v1.8

一. coffee-script的github master分支已经是1.8版本, 对es6很多语法都做了支持, 其中就包括generator.

1
# Function
( test ) ->
  ....

# GeneratorFuntion
( test ) ->
  yield ->

二. 使用git地址安装npm包

package.json

1
{
  .......
  dependencies : {
    "coffee-script" : "git+ssh://git@github.com:jashkenas/coffeescript.git"
  }
}

//git://github.com/user/project.git#commit-ish
//git+ssh://user@hostname:project.git#commit-ish
//git+ssh://user@hostname/project.git#commit-ish
//git+http://user@hostname/project/blah.git#commit-ish
//git+https://user@hostname/project/blah.git#commit-ish

三. 同时, 1.8的coffee修正了一些逻辑, 偷懒的同学注意了

1
( @server ) ->

对于如上代码,1.8 版本以前的coffee会编译成

1
  fucntion( server ){
  @server = server
.....
}

但是对于1.8版本coffee会编译成:

1
function( _at_server ){
  @server = _at_server
  .....
}

如果直接在函数中使用server变量的话,1.8版本之前是可以的,但是在1.8版本之后就会报错了。

如何使用hexo搭建一个个人博客

如何快速搭建一个hexo博客。

一. 安装hexo

1
npm install hexo -g

二. 初始化hexo目录

1
mkdir hexo
cd hexo 
hexo init
npm install

三. 所有的博客文章都在source->_posts文件夹下面, 一个markdown文件就是一篇博客, 执行一下命令, 就能够在本地起一个服务预览一下。

1
# 将markdown文件编译成html
hexo generate
# 启动服务
hexo server

然后访问: http://127.0.0.1:4000, 就可以查看博客预览了。

四. 注册一个github账号, 创建一个一下名字的项目:#{github name}.github.io, 例如perterpon.github.io.

五. 修改hexo配置文件, 配置文件位于/_config.yml

e.g

1
deploy: 
  # type 选择github
  type: github
  # 填写github地址
  repository: https://github.com/zippera/zippera.github.io.git
  # 选择相应分支, 使用github pages的时候一般默认是使用gh-pages分支作为使用的分支, 可以在github上设置.
  branch: master

六. 创建github pages, 在之前创建好perterpon.github.io项目中, 点击settings- Automatic page generator按钮, 跟着提示一路确定就能完成创建了。

七. 在hexo文件夹中, 执行hexo deploy, 提示成功后等待几分钟访问http://perterpon.github.io, 就能访问自己的博客了。

八. 大量主题样式等等, 评论系统: 多说.

OSX 中获取UID和MAC地址的方法

很多时候需要搜集用户的MAC地址或者UID来进行用户的身份确认, UID和MAC地址都可以保持唯一性.

  • 获取UID
1
+ ( NSString *) getHardwareUUID {
  NSTask *task;
  task = [[NSTask alloc] init];
  [task setLaunchPath: @"/usr/sbin/ioreg"];
  
  //ioreg -rd1 -c IOPlatformExpertDevice | grep -E '(UUID)'
  
  NSArray *arguments;
  arguments = [NSArray arrayWithObjects: @"-rd1", @"-c",@"IOPlatformExpertDevice",nil];
  [task setArguments: arguments];
  
  NSPipe *pipe;
  pipe = [NSPipe pipe];
  [task setStandardOutput: pipe];
  
  NSFileHandle *file;
  file = [pipe fileHandleForReading];
  
  [task launch];
  
  NSData *data;
  data = [file readDataToEndOfFile];
  
  NSString *string;
  string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
  
  NSString *key = @"IOPlatformUUID";
  NSRange range = [string rangeOfString:key];
  
  NSInteger location = range.location + [key length] + 5;
  NSInteger length = 32 + 4;
  range.location = location;
  range.length = length;
  
  NSString *UUID = [string substringWithRange:range];
  
  UUID = [UUID stringByReplacingOccurrencesOfString:@"-" withString:@""];
  
  return UUID;
}
  • 获取MAC地址
1
+ ( NSMutableString *) getMacAddr {
  kern_return_t kr;
  CFMutableDictionaryRef matchDict;
  io_iterator_t iterator;
  io_registry_entry_t entry;

  matchDict = IOServiceMatching("IOEthernetInterface");
  kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchDict, &iterator);

  NSDictionary *resultInfo = nil;

  NSMutableString *macAddrs = [[ NSMutableString alloc ] init];

  while ((entry = IOIteratorNext(iterator)) != 0)
  {
      CFMutableDictionaryRef properties=NULL;
      kr = IORegistryEntryCreateCFProperties(entry,
                                             &properties,
                                             kCFAllocatorDefault,
                                             kNilOptions);
      if (properties)
      {
          resultInfo = (__bridge_transfer NSDictionary *)properties;
          NSString *bsdName = [resultInfo objectForKey:@"BSD Name"];
          NSData *macData = [resultInfo objectForKey:@"IOMACAddress"];
          if (!macData)
          {
              continue;
          }

          NSMutableString *macAddress = [[NSMutableString alloc] init];
          const UInt8 *bytes = [macData bytes];
          for (int i=0; i<macData.length; i++)
          {
              [macAddress appendFormat:@"%02x",*(bytes+i)];
          }
          if (bsdName && macAddress)
          {
              [ macAddrs appendFormat:@"%@,", macAddress ];
          }
      }
  }
  IOObjectRelease(iterator);
  return macAddrs;
}