python中如何自定义解析域名

说起这个问题就觉得蛋疼,这个问题可是折腾了我一天一夜的说。。

幸好在知乎上遇到各路高人,在大家的帮助下总算把问题解决了,现在拿出来分享一下~

先描述一下遇到的问题:

学校的信息门户和教务系统域名分别是portal.uestc.edu.cn和ea.uestc.edu.cn,但是内外网访问的ip地址不一样。

信息门户    portal.uestc.edu.cn   out:125.71.228.241 in:222.197.164.72

教务系统    ea.uestc.edu.cn       out:125.71.228.243 in:222.197.164.82

当我们身处内网的时候,访问这两个网站妥妥的,科大的DNS还是蛮给力的。

但是当我们处于校外的时候,就会发现访问这两个网站变得异常的艰难,经常是无法解析域名,或是域名解析成其它IP地址了。(不过发生这种情况还要区分身处哪个省,因为大家的DNS都不一样。。。)

说了这么多前提,是时候说正题了。

最近在用python写一个查成绩的客户端,由于学校特殊的身份验证方式,需要依次访问上面的两个网站。

如果让python访问只由域名构成的url,在校内还是无压力的,但到了校外肯定就是各种403、404,或者直接Connection refused。。

那么直接用ip去代替url中的域名又行不行呢?答案是否定的,因为这两个服务器上面都不只是一个网站,而且都不是在主目录下。

上面就是我所遇到的问题以及我能想到的解决方法(其实没解决!)

在各种看官方文档以及各种搜索都无果之后,我决定在知乎上求助,希望有牛人指导一下我 T_T

我在知乎上提出的问题的传送门:http://www.zhihu.com/question/21054889

感谢知乎上的@狼大人,在他的帮助下解决了上面的问题。下面就来详细讲一下是如何解决的吧~

 


 

第一种解决方案是利用http数据包头部中的“Host”属性。

在发送HTTP请求的时候,数据包的头部总是会带上各种各样的属性,比如Data、Referer、Cookie等。(Quick reference to HTTP headers

其中的Host属性是指,当前访问资源对应的主机名和端口号。

假设我们要访问的url是http://hack0nair.me/wp-admin/ ,需要域名固定解析为 127.0.0.1 这个IP。

使用python中urllib库的request做法是:

urllib.request(url="http://127.0.0.1/wp-admin/", headers={"Host" : "hack0nair.me"})

访问会产生如下的数据包:

GET http://127.0.0.1/wp-admin/ HTTP/1.1

Host: hack0nair.me
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:16.0) Gecko/20100101 Firefox/16.0
Accept: */*
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://hack0nair.me/

 这样的访问会告诉127.0.0.1服务器,当前需要访问的/wp-admin是hack0nair.me这个域名的资源。

看来问题已经解决了,其实未必。

对于不需要cookie支持的访问来说,问题确实解决了。但如果是需要多次访问并要验证cookie的话,这样做是不行的。因为这样访问产生的cookie对应的作用域是IP而不是域名!于是就有了下面一种更完美的解决方案。

第二种解决方案是直接修改socket的address。

方法来自StackOverFlow的MattH:http://stackoverflow.com/questions/2236498/tell-urllib2-to-use-custom-dns

具体的意思是,建立HTTP连接的时候仍然使用域名,但是通过修改底层socket的address来达到目的。

这里理解起来需要一点计算机网络的基础。在建立HTTP连接的时候,在应用层必然会使用一个socket跟远方服务器进行通信,而socket建立的时候需要指定服务器的地址,这时候我们只需要填入我们预设的IP地址即可。由于应用层是不会去关心它的底层如何建立连接的,它只知道自己正在对哪个域名进行访问,这样就相当于“欺骗”了处于上层的HTTP服务。(我的语言表达能力略拙计的说= =)

大概就是上面这个意思了,那么要在python中实现上面的想法,首先需要建立一个httplib.HTTPConnection的子类,在里面定义一个connect方法求修改socket.create_connection的默认参数;然后继承HTTPHandler这个类,重写里面的http_open方法,使得http_open去调用第一步的子类;最后就是把HTTPHandler放到我们的opener里面。

MattH同时给出了HTTP和HTTPS的修改方案,不过我只要HTTP的修改就够了。下面是经过我修改的例子:

def MyHost(host):
  if host == 'hack0nair.me':
    return '127.0.0.1' # 指定的IP地址
  else:
    return host

class MyHTTPConnection(httplib.HTTPConnection):
  def connect(self):
    self.sock = socket.create_connection((MyHost(self.host),self.port),self.timeout)

class MyHTTPHandler(urllib2.HTTPHandler):
  def http_open(self,req):
    return self.do_open(MyHTTPConnection,req)

opener = urllib2.build_opener(MyHTTPHandler)
urllib2.install_opener(opener)

url = 'http://hack0nair.me/wp-admin'
req = request.Request(url)
req = f.read()

 上面例子的含义是,当opener遇到域名是hack0nair.me时,MyHTTPHandler会让所建立的socket的address为指定的IP地址。由于没有对urllib相关对象进行修改,所以HTTP神马都不知道~

如果访问要带上cookie,那么还要加上HTTPCookieProcessor这个handler。

« 返回