#
摘要: 背景
详见《Hosts绑定新思路之DNS代理篇》
核心内容
1. DNS协议解析
2. 启动UDP服务,监听53端口
3. 根据DB或者文本,进行Hosts解析
DNS协议
DNS Protocol Overview (推荐)
非强详细,但是不怎么看得懂的长篇大论
如果没有耐心的同学,可以看看我通过wireshark分析之后制作的两张gif图片。大概能知道DNS协议的...
阅读全文
前言
此文摘自2011年5月23日邮件分享,为《Hosts绑定新思路之HTTP代理篇》续集
电视有续集,电影也有续集,Hosts绑定思路同样有续集.
我们先用一句话来回顾下,上集中关于Hosts绑定的思路:
原理:利用Http代理的方式,将分散在各个客户端的Hosts绑定,集中绑定在Http代理服务器上
优点:集中管理
缺点:一台Http代理服务器,只能绑定一组Hosts信息
(详细内容,请见之前的邮件)
在当时描述方案邮件的时候,也意识到了方案存在的不足,所以一直在思考改进方案(详见之前邮件中最后一节—改进方案思路).
经过一段时间的思考,改进方案有了大概的雏形: 将之前的HTTP代理方案 替换成 DNS代理方案
俗话说得好:有图有真相.先贴上一张架构图,之后再用文字慢慢解释
架构中核心组件是:DNS BackOffice服务器 和DNS代理服务器
DNS BackOffice服务器的作用有:
1. 开发/测试管理员通过BackOffice服务维护各自项目的绑定信息,BackOffice服务将之持久化 (图中 蓝色虚线)
2. 开发/测试人员通过BackOffice服务,告知需要哪个项目的绑定信息,BackOffice服务将之持久化 (图中 黑色虚线)
DNS代理服务器的作用有:
1. 拦截Domain Name的解析.通过来源IP判断需要绑定的Hosts信息,通过File/DB得到对应的IP,通过DNS协议返回 (图中 红色实线 和 黑色实线)
2. 如果不在绑定之列,则请求上级DNS服务器,返回其Response.
此方案的优势:
1. 本地Hosts绑定优先.
只要本地Hosts有绑定IP,则不会请求DNS代理服务器.只请求本地Hosts文件.能满足个性化需求.
2. DNS代理服务器支持多种绑定方式,如通配符,正则等
对于目前旺铺,完全可以使用通配符,如 *.cn.alibaba.com,简化配置工作量
3. 操作简单
只要将DNS服务器设置成DNS代理服务器IP即可 (附录中有详细说明)
4. 有效利用现有成果
目前测试同学已经集中维护了Hosts绑定信息,只要部署DNS代理服务器,并做简单的集成即可
5. DNS代理服务器代码轻量小巧,易于修改扩展
目前一共只有212行代码,其中DNS协议部分130行,DNS代理部分82行.
附录
I. 客户端如何设置DNS服务器
Windows用户,见图:
Linux用户,见图:
修改 /etc/resolv.conf文件即可
前言
此文摘自2011年3月22日邮件分享
现状
平时开发,测试,功能预演阶段,为了能够正常访问应用,需要做Hosts绑定.随着应用数量的不断增多,绑定量也是急剧上升.例如最近工作平台三期项目,需要绑定的环境多达44个.一旦有变动,需要通知所有人员做本地Hosts的调整,维护成本那是相当地大.
用一张图,来描述下目前我们的方案:
如果站在面向对象编程的角度,来思考这张图,我们会发现.
1. 利用客户端本地Hosts绑定来实现,并且客户端数量不可控—利用客户端解决需求,但客户端维护不在可控范围内
2. Hosts绑定是非常不稳定的—需求易变
这样的设计,违反了”封装变化”的设计原则,故一旦有变动,维护成本非常大.
新方案思路
按照”封装变化”的设计原则,我们就应该把”域名绑定”这个易变需求,进行统一管理.
看上图,我们会发现,DNS的职责就是做域名解析的,并且DNS管理比较可控.
于是第一反应,我们可以使用内部域名解析服务器来绑定这些域名.
但是问题又来了,DNS来做测试环境域名解析,太重量级了.同一个域名,对应测试服务器IP有多个,绑定哪一个好呢?并且域名对应IP不断变化,IT DNS负责人不被我们累死啊?
既然DNS上做文章不可行,又需要统一管理的地方,那么我们只能再抽象出一个新的概念来.
同样,我们利用一张图,来描述下整体架构.
与上图相对,此图多了一个”代理服务器”的概念,即Hosts绑定动作在此概念上完成.
流程如下:
1. 客户端浏览器设置代理服务器,将所有请求发送到代理服务器上
2. 代理服务器检查本地Hosts绑定,如绑定则直接解析,反之进入流程3
3. 代理服务器通过内部域名服务器解析域名
4. 代理服务器发送请求到测试服务器上,并且将响应内容返回给客户端
具体尝试性实施方案如下(在XX项目过程中有成功案例)
1. 利用squid搭建代理服务器 (代理地址: 10.20.131.207:3128)
备注:
Squid配置介绍见附录I
2. 浏览器配置代理
全局代理: 代理服务器上,直接填写 10.20.131.207 3128
局部代理: 通过pac实现,选择”使用自动配置脚本”,脚本格式内容如下:
备注:
Pac脚本详细介绍见附录II
为了防止将配置工作带给PD,销售等,我们可以使用配置好的绿色浏览器提供直接使用.
推荐一款:GreenBrowser: http://www.morequick.com/indexen.htm
IE具体配置,见下图:
Firefox同样支持代理和pac脚本
Chrome需要安装proxy switchy插件来支持.
改进方案思路
上述的方案中,有两个比较大的缺陷
1. 代理服务器没有多实例概念
代理服务器通过hosts绑定.hosts是全局性的,意味着一台代理服务器只能服务一组需求.而事实上,我们不同的项目需要的绑定都是不一样的.
2. 特性化需求不能满足
绑定全在代理服务器上做了,客户端本地个性化需求无法支持
所以,我理想中整体架构是这样的,见图:
1. 优先查看本地hosts文件
2. 代理服务器支持多实例部署,不同实例有不同的hosts绑定配置.
目前具体实现方案,还在构思中.欢迎大家提供实现方案思路.
附录I
Squid权威指南(中文版): http://home.arcor.de/pangj/squid/chap01.html
附录II
Pac介绍: http://en.wikipedia.org/wiki/Proxy_auto-config
Pac函数介绍: http://findproxyforurl.com/pac_functions_explained.html
前段时间替朋友做了一个物业管理系统,使用了python+django技术,对django有了一些了解。
作为一个一直来使用java的人来说,初次使用django,真正体会到了简单美学。(一共13个功能,不到500行代码)
此文,主要总结下django框架的一些扩展点:
MIDDLEWARE_CLASSES
在request请求之前,或者response请求之后,做拦截,允许自定义逻辑。有些类似J2EE Servlet中的Filter概念。
TEMPLATE_CONTEXT_PROCESSORS
进入模板渲染之前,允许放入一组用于模板渲染的Key-Value属性。
TEMPLATE FILTER
模板中的管道语法,通过自定义行为,添加用于显示的一些逻辑。
TEMPLATE TAG
模板tag,添加一组行为。有些类似Velocity中的ToolSet功能。
模板tag+指定模板,充当页面组件(widgets)功能
middleware演示
1 from django.db import connection
2 from django.http import HttpResponseRedirect
3
4 #拦截response请求之后,打印请求中的所有sql
5 class SqlLogMiddleware(object):
6 def process_response(self, req, res):
7 for sql in connection.queries:
8 print sql
9 return res
10
11 #拦截request请求之前,做权限校验
12 class Auth(object):
13 def process_request(self, req):
14 if req.path == '/admin/':
15 return
16 if not req.user.is_authenticated():
17 return HttpResponseRedirect('/admin/')
18
1 MIDDLEWARE_CLASSES = (
2 'django.middleware.common.CommonMiddleware',
3 'django.contrib.sessions.middleware.SessionMiddleware',
4 'django.contrib.auth.middleware.AuthenticationMiddleware',
5 'finance.middleware.SqlLogMiddleware',
6 'finance.middleware.Auth',
7 )
template context processor演示
1 def version(request):
2 return {'name':'Stone.J',
3 'version':'1.0-beata',
4 'date':'2011-03-20'}
1 TEMPLATE_CONTEXT_PROCESSORS = (
2 'django.core.context_processors.request',
3 'django.core.context_processors.auth',
4 'django.core.context_processors.debug',
5 'django.core.context_processors.i18n',
6 'django.core.context_processors.media',
7 'finance.example.context_processors.version',
8 )
template filter演示
1 def row(value):
2 if not value:
3 return 'row1'
4 if value % 2 == 1:
5 return 'row1'
6 else:
7 return 'row2'
8
9 def math_mul(value, num):
10 return value * num
11
12 def math_add(value, num):
13 return value + num
14
15 register = template.Library()
16 register.filter('row', row)
17 register.filter('math_add', math_add)
18 register.filter('math_mul', math_mul)
1 {% load my_filter %}
2 {% for c in page.object_list %}
3 <tr class="{{ forloop.counter|row }}">
4 <td>{{ c.amount | math_add:c.amount2}}</td>
5 <td>{{ c.amount | math_mul:12}}</td>
6 </tr>
7 {% endfor %}
通过约定的方式,在任意一个app下,建立一个templatetags目录,会自动寻找到。(不过没有命名空间,是一个比较猥琐的事情,容易造成不同app下的冲突)
template tag演示
1 register = template.Library()
2
3 class AccountNode(template.Node):
4 def __init__(self, name):
5 self.name = name
6
7 def render(self, context):
8 context[self.name] = Account.objects.get()
9 return ''
10
11 def get_account(parser, token):
12 try:
13 tag_name, name = token.split_contents()
14 except ValueError:
15 raise template.TemplateSyntaxError, "%s tag requires argument" % tag_name
16 return AccountNode(name)
17
18 register.tag('get_account', get_account)
1 {% load my_tag %}
2 {% get_account account %}<!-- 通过tag取到内容赋值给account变量 -->
3 {{ account.amount }}
template tag + template file演示
1 from django import template
2 register = template.Library()
3
4 def version(context):
5 return {'name':'Stone.J',
6 'version':'1.0-beata',
7 'date':'2011-03-20'}
8
9 register.inclusion_tag('example/version.html', takes_context=True)(version)
1 <!-- 这份内容可以被当成widget复用 -->
2 <table>
3 <tr>
4 <td>{{ name }}</td>
5 <td>{{ version }}</td>
6 <td>{{ data }}</td>
7 </tr>
8 </table>
9
tag寻找模式等同于filter。
接上文,继续show下我命令行下的工具--翻译脚本
(利用了google 翻译 json api:
http://translate.google.cn/translate_a/t?client=t&text=%s&hl=zh-CN&sl=%s&tl=%s)
特性:
1. 自动识别中翻英/英翻中
2. 翻译
涉及技术:
1. python
2. urllib
3. json
4. re
截图:
对应代码:
1 '''
2 Created on 2010-11-28
3
4 @author: stone
5 '''
6 import json
7 import re
8 import sys
9 import urllib2
10 import types
11
12 res = 'http://translate.google.cn/translate_a/t?client=t&text=%s&hl=zh-CN&sl=%s&tl=%s'
13 agent = 'Mozilla / 5.0 (X11; U; Linux i686; en - US) AppleWebKit / 534.7 (KHTML, like Gecko) Chrome / 7.0.517.44 Safari / 534.7'
14
15 def get_data(text, sl='en', tl='zh-CN'):
16 req = urllib2.Request(res % (urllib2.quote(text), sl, tl))
17 req.add_header('user-agent', agent)
18 content = urllib2.urlopen(req).read()
19 return json.loads(to_standard_json(content))
20
21 def show(data):
22 #step1
23 print u'翻译:\n %s' % (data[4][0][0])
24 #step2
25 if types.ListType == type(data[1]):
26 print u'\n字典:'
27 for word in data[1]:
28 print word[0]
29 if len(word) > 1:
30 for i, w in enumerate(word[1]):
31 print ' %s.%s' % (i + 1, w)
32
33 def to_standard_json(json):
34 p = re.compile(r',([,\]])')
35 while(p.search(json)):
36 json = p.sub(lambda m:',null%s' % (m.group(1)), json)
37 return json
38
39 def contains_cn(text):
40 for c in text:
41 if ord(c) > 127:
42 return True
43 return False
44
45 if __name__ == '__main__':
46 if not len(sys.argv) == 2 or not sys.argv[1].strip():
47 print 'Useage:translate.py word'
48 sys.exit()
49 word = sys.argv[1].strip()
50 if contains_cn(word):
51 show(get_data(word, 'zh-CN', 'en'))
52 else:
53 show(get_data(word, 'en', 'zh-CN'))
按照同事的话说,我是一个十足的命令控。
利用最近项目通宵发布的空闲时间中,写了一个命令行下的音乐播放器,以满足我在linux命令下的需求。
播放器利用技术:
Python+GST(
http://gstreamer.freedesktop.org/modules/gst-python.html)+Console解析
播放器自持操作:
1. 播放
2. 下一首
3. 上一首
4. 暂停
5. 查看播放列表信息
6. 查看当前播放信息
7. 停止(退出)
看一张截图:
通过分析meliae dump出来的内存信息,差不做占用2.5M内存,算的上比较小巧了。
对应代码:(需要安装py-gst,ubuntu下:sudo apt-get install python-gst0.10)
1 #!/usr/bin/env python
2
3 import gst
4 import gobject
5 import sys
6 #to avoid eclipse'warning
7 eval('gobject.threads_init()')
8 from threading import Thread
9
10 class AudioPlayer:
11
12 EVENT_PLAY_NEW = 1
13
14 def __init__(self, advisor):
15 self.main = gobject.MainLoop()
16 self.player = gst.element_factory_make('playbin', 'player')
17 self.index = -1
18 self.list = None
19 self.advisor = advisor
20
21 bus = self.player.get_bus()
22 bus.add_signal_watch()
23 bus.connect('message', self.on_message)
24
25 Thread(target=self.main.run).start()
26
27 def add_list(self , list=[]):
28 if list is None:
29 list = []
30 self.list = [(i, l.strip(), l[l.rfind('/') + 1:]) for (i, l) in enumerate(list)]
31
32 def play(self, index=None):
33 #play specified tracks
34 if 0 <= index < len(self.list):
35 self.index = index
36 self.player.set_state(gst.STATE_NULL)
37 self.player.set_property('uri', self.list[index][1])
38 self.player.set_state(gst.STATE_PLAYING)
39 if self.advisor:
40 self.advisor.on_message(AudioPlayer.EVENT_PLAY_NEW, (self.index, self.get_title()))
41 #resume playing
42 if index is None:
43 if self.index > -1:
44 self.player.set_state(gst.STATE_PLAYING)
45
46 def pause(self):
47 self.player.set_state(gst.STATE_PAUSED)
48
49 def stop(self):
50 self.player.set_state(gst.STATE_NULL)
51 self.main.quit()
52
53 def get_title(self):
54 if self.index == -1 or len(self.list) == 0:
55 return None
56 return self.list[self.index][2]
57
58 def get_previous(self):
59 if self.index == -1 or len(self.list) == 0:
60 return - 1
61 if self.index == 0:
62 return 0
63 return self.index - 1
64
65 def get_next(self):
66 if len(self.list) == 0:
67 return - 1
68 if self.index + 1 == len(self.list):
69 return 0
70 return self.index + 1
71
72 def on_message(self, bus, message):
73 t = message.type
74 if t == gst.MESSAGE_ERROR:
75 self.play(self.get_next())
76 elif t == gst.MESSAGE_EOS:
77 self.play(self.get_next())
78
79 class Console:
80
81 def __init__(self, list):
82 self.player = AudioPlayer(self)
83 self.player.add_list(list)
84 self.player.play(0)
85
86 Thread(target=self.run).start()
87
88 def run(self):
89 while(True):
90 self.on_cmd(raw_input())
91
92 def on_cmd(self, cmd):
93 if cmd is None:
94 return
95 if cmd.startswith('play'):
96 self.player.play()
97 elif cmd.startswith('next'):
98 self.player.play(self.player.get_next())
99 elif cmd.startswith('previous'):
100 self.player.play(self.player.get_previous())
101 elif cmd.startswith('pause'):
102 self.player.pause()
103 elif cmd.startswith('list'):
104 print '====================================='
105 for info in self.player.list:
106 print '%s. %s' % (info[0], info[2])
107 print '====================================='
108 elif cmd.startswith('info'):
109 print '====================================='
110 print '%s. %s' % (self.player.index, self.player.get_title())
111 print '====================================='
112 elif cmd.startswith('stop'):
113 self.player.stop()
114 sys.exit(0)
115 elif cmd.startswith('dump'):
116 from meliae import scanner
117 scanner.dump_all_objects('./dump.txt')
118 else:
119 print '''=====================================
120 Usage:
121 play
122 next
123 previous
124 pause
125 list
126 info
127 stop
128 dump
129 ====================================='''
130
131 def on_message(self, event, info):
132 if event == AudioPlayer.EVENT_PLAY_NEW:
133 print '====================================='
134 print 'Tracks: %s.%s' % (info[0], info[1])
135 print '====================================='
136
137
138 if len(sys.argv) != 2:
139 print 'player.py mp3.list'
140 sys.exit(-1)
141 list = [l.strip() for l in open(sys.argv[1]).readlines() if l.strip() != '']
142 Console(list)
下载
推荐一个eclipse插件--全屏插件(显示器整个屏幕)。
发觉这个东东还是挺不错的,尤其对于本本的同学,特别实用。
在我自己的本本上,发现一旦使用全屏,能多显示8行代码,多了21%左右,挺可观的。
插件地址:http://code.google.com/p/eclipse-fullscreen/
给个图:
HttpClient使用过程中的安全隐患,这个有些标题党。因为这本身不是HttpClient的问题,而是使用者的问题。
安全隐患场景说明:
一旦请求大数据资源,则HttpClient线程会被长时间占有。即便调用了org.apache.commons.httpclient.HttpMethod#releaseConnection()方法,也无济于事。
如果请求的资源是应用可控的,那么不存在任何问题。可是恰恰我们应用的使用场景是,请求资源由用户自行输入,于是乎,我们不得不重视这个问题。
我们跟踪releaseConnection代码发现:
org.apache.commons.httpclient.HttpMethodBase#releaseConnection()
1 public void releaseConnection() {
2 try {
3 if (this.responseStream != null) {
4 try {
5 // FYI - this may indirectly invoke responseBodyConsumed.
6 this.responseStream.close();
7 } catch (IOException ignore) {
8 }
9 }
10 } finally {
11 ensureConnectionRelease();
12 }
13 }
org.apache.commons.httpclient.ChunkedInputStream#close()
1 public void close() throws IOException {
2 if (!closed) {
3 try {
4 if (!eof) {
5 exhaustInputStream(this);
6 }
7 } finally {
8 eof = true;
9 closed = true;
10 }
11 }
12 }
org.apache.commons.httpclient.ChunkedInputStream#exhaustInputStream(InputStream inStream)
1 static void exhaustInputStream(InputStream inStream) throws IOException {
2 // read and discard the remainder of the message
3 byte buffer[] = new byte[1024];
4 while (inStream.read(buffer) >= 0) {
5 ;
6 }
7 }
看到了吧,所谓的丢弃response,其实是读完了一次请求的response,只是不做任何处理罢了。
想想也是,HttpClient的设计理念是重复使用HttpConnection,岂能轻易被强制close呢。
怎么办?有朋友说,不是有time out设置嘛,设置下就可以下。
我先来解释下Httpclient中两个time out的概念:
1.public static final String CONNECTION_TIMEOUT = "http.connection.timeout";
即创建socket连接的超时时间:java.net.Socket#connect(SocketAddress endpoint, int timeout)中的timeout
2.public static final String SO_TIMEOUT = "http.socket.timeout";
即read data过程中,等待数据的timeout:java.net.Socket#setSoTimeout(int timeout)中的timeout
而在我上面场景中,这两个timeout都不满足,确实是由于资源过大,而占用了大量的请求时间。
问题总是要解决的,解决思路如下:
1.利用DelayQueue,管理所有请求
2.利用一个异步线程监控,关闭超长时间的请求
演示代码如下:
1 public class Misc2 {
2
3 private static final DelayQueue<Timeout> TIMEOUT_QUEUE = new DelayQueue<Timeout>();
4
5 public static void main(String[] args) throws Exception {
6 new Monitor().start(); // 超时监控线程
7
8 new Request(4).start();// 模拟第一个下载
9 new Request(3).start();// 模拟第二个下载
10 new Request(2).start();// 模拟第三个下载
11 }
12
13 /**
14 * 模拟一次HttpClient请求
15 *
16 * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> 2011-4-9
17 */
18 public static class Request extends Thread {
19
20 private long delay;
21
22 public Request(long delay){
23 this.delay = delay;
24 }
25
26 public void run() {
27 HttpClient hc = new HttpClient();
28 GetMethod req = new GetMethod("http://www.python.org/ftp/python/2.7.1/Python-2.7.1.tgz");
29 try {
30 TIMEOUT_QUEUE.offer(new Timeout(delay * 1000, hc.getHttpConnectionManager()));
31 hc.executeMethod(req);
32 } catch (Exception e) {
33 System.out.println(e);
34 }
35 req.releaseConnection();
36 }
37
38 }
39
40 /**
41 * 监工:监控线程,通过DelayQueue,阻塞得到最近超时的对象,强制关闭
42 *
43 * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> 2011-4-9
44 */
45 public static class Monitor extends Thread {
46
47 @Override
48 public void run() {
49 while (true) {
50 try {
51 Timeout timeout = TIMEOUT_QUEUE.take();
52 timeout.forceClose();
53 } catch (InterruptedException e) {
54 System.out.println(e);
55 }
56 }
57 }
58
59 }
60
61 /**
62 * 使用delay queue,对Delayed接口的实现 根据请求当前时间+该请求允许timeout时间,和当前时间比较,判断是否已经超时
63 *
64 * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> 2011-4-9
65 */
66 public static class Timeout implements Delayed {
67
68 private long debut;
69 private long delay;
70 private HttpConnectionManager manager;
71
72 public Timeout(long delay, HttpConnectionManager manager){
73 this.debut = System.currentTimeMillis();
74 this.delay = delay;
75 this.manager = manager;
76 }
77
78 public void forceClose() {
79 System.out.println(this.debut + ":" + this.delay);
80 if (manager instanceof SimpleHttpConnectionManager) {
81 ((SimpleHttpConnectionManager) manager).shutdown();
82 }
83 if (manager instanceof MultiThreadedHttpConnectionManager) {
84 ((MultiThreadedHttpConnectionManager) manager).shutdown();
85 }
86 }
87
88 @Override
89 public int compareTo(Delayed o) {
90 if (o instanceof Timeout) {
91 Timeout timeout = (Timeout) o;
92 if (this.debut + this.delay == timeout.debut + timeout.delay) {
93 return 0;
94 } else if (this.debut + this.delay > timeout.debut + timeout.delay) {
95 return 1;
96 } else {
97 return -1;
98 }
99 }
100 return 0;
101 }
102
103 @Override
104 public long getDelay(TimeUnit unit) {
105 return debut + delay - System.currentTimeMillis();
106 }
107
108 }
109
110 }
本来还想详细讲下DelayQueue,但是发现同事已经有比较纤细的描述,就加个链接吧 (人懒,没办法)
http://agapple.iteye.com/blog/916837
http://agapple.iteye.com/blog/947133
备注:
HttpClient3.1中,SimpleHttpConnectionManager才有shutdown方法,3.0.1中还存在 :)
最近的项目中,使用到了HtmlParser(1.5版本).在使用过程中(如访问url为:
http://athena2002.vip.china.alibaba.com/
),遇到了异常:
Exception in thread "main" java.lang.IllegalArgumentException: invalid cookie name: Discard
at org.htmlparser.http.Cookie.<init>(Cookie.java:136)
at org.htmlparser.http.ConnectionManager.parseCookies(ConnectionManager.java:1126)
at org.htmlparser.http.ConnectionManager.openConnection(ConnectionManager.java:621)
at org.htmlparser.http.ConnectionManager.openConnection(ConnectionManager.java:792)
at org.htmlparser.Parser.<init>(Parser.java:251)
at org.htmlparser.Parser.<init>(Parser.java:261)
检查代码,发现:
org.htmlparser.http.Cookie
1 public Cookie (String name, String value)
2 {
3 if (!isToken (name) || name.equalsIgnoreCase ("Comment") // rfc2019
4 || name.equalsIgnoreCase ("Discard") // 2019++
5 || name.equalsIgnoreCase ("Domain")
6 || name.equalsIgnoreCase ("Expires") // (old cookies)
7 || name.equalsIgnoreCase ("Max-Age") // rfc2019
8 || name.equalsIgnoreCase ("Path")
9 || name.equalsIgnoreCase ("Secure")
10 || name.equalsIgnoreCase ("Version"))
11 throw new IllegalArgumentException ("invalid cookie name: " + name);
12 mName = name;
13 mValue = value;
14 mComment = null;
15 mDomain = null;
16 mExpiry = null; // not persisted
17 mPath = "/";
18 mSecure = false;
19 mVersion = 0;
20 }
一旦发现name值为“Discard”,则抛异常。
而在org.htmlparser.http.ConnectionManager.parseCookies (URLConnection connection) 解析cookie的代码中,见代码片段
if (key.equals ("domain"))
cookie.setDomain (value);
else
if (key.equals ("path"))
cookie.setPath (value);
else
if (key.equals ("secure"))
cookie.setSecure (true);
else
if (key.equals ("comment"))
cookie.setComment (value);
else
if (key.equals ("version"))
cookie.setVersion (Integer.parseInt (value));
else
if (key.equals ("max-age"))
{
Date date = new Date ();
long then = date.getTime () + Integer.parseInt (value) * 1000;
date.setTime (then);
cookie.setExpiryDate (date);
}
else
{ // error,? unknown attribute,
// maybe just another cookie not separated by a comma
cookie = new Cookie (name, value); //出问题的地方
cookies.addElement (cookie);
}
没有对Discard做特殊处理。
无奈之下,覆写了此方法,加上对Discard的处理--直接continue :)
今天在写blog的时候,拿了1.6的代码测试,发现没有问题,分析代码后发现
1. ConnectionManager parserCookie之前,加了条件判断
if (getCookieProcessingEnabled ())
parseCookies (ret);
默认情况下,条件为false
2. parserCookie的时候,catch了异常
1 // error,? unknown attribute,
2 // maybe just another cookie
3 // not separated by a comma
4 try
5 {
6 cookie = new Cookie (name,
7 value);
8 cookies.addElement (cookie);
9 }
10 catch (IllegalArgumentException iae)
11 {
12 // should print a warning
13 // for now just bail
14 break;
15 }
虽然解决了问题,但是明显还没有意识到Discard的问题。
从我的理解看,最合理的解决方案是:
1. org.htmlparser.http.Cookie中添加 boolean discard方法
2. org.htmlparser.http.ConnectionManager parserCookies()方法,对Discard做处理,如有值,则设置cookie.discard=true
关于discard的解释,见
http://www.faqs.org/rfcs/rfc2965.html:
Discard
OPTIONAL. The Discard attribute instructs the user agent to
discard the cookie unconditionally when the user agent terminates