python http proxy (json Access to restricted URI denied)
Últimamente he estado jugando con JQuery y llamadas ajax a servcios json y me he encontrado con un pequeño problema. Cuando intentas acceder a un servicio que te ofrece json plano jquery peta y te devuelve un error como este en la consola de javascript del navegador:
Error: uncaught exception: [Exception... "Access to restricted URI denied" code: "1012" nsresult: "0x805303f4 (NS_ERROR_DOM_BAD_URI)" location: "js/jquery-1.3.2.min.js Line: 19"]
Esto ocurre porque el navegador implementa una política de seguridad mediante la cual no permite que se acceda a urls diferentes a la que hace la petición. Osea que no puedes hacer peticiones a un servidor externo de json pelado.
Para realizar este tipo de peticiones existen callbacks y jsonp. La idea es que en la url de la petición jquery debes añadir "http://.../?callback=?" y jquery rellenará el resto. Para cada petición jquery pasará como callback un nombre de función "jsonp1234" donde los números son diferentes por petición y el servidor ha de responder con el código json envuelto en esa función "jsonp1234({...});".
¿Qué haces cuando el servidor no ofrece el servicio jsonp?
Aquí es donde viene el proxy guarrón que me he implementado. La idea es pillar todas las peticiones al servidor, pillar el json plano y modificarlo para que sea jsonp.
Para ello lo primero que desarrollé fué un proxy http plano y feo:
[python]
# python proxy.py twitter.com 80
import sys
import socket
s = socket.socket()
mserver = 'localhost'
mport = 8080
s.bind((mserver, mport))
s.listen(10)
server = sys.argv[1]
port = int(sys.argv[2])
while True:
try:
print "waiting for requests at %s:%s" % (mserver, mport)
s1 = s.accept()
except KeyboardInterrupt:
s.close()
print "bye"
break
s2 = socket.socket()
s2.connect((server, port))
s1 = s1[0]
v = ''
r = 0
while not r or len(r) == 1024:
r = s1.recv(1024)
v += r
v = v.replace('Host: %s:%s' %(mserver, mport), 'Host: %s:%s' % (server, port))
print v
s2.send(v)
v2 = ''
r = 0
while True:
s2.settimeout(0.3)
try:
r = s2.recv(1024)
except:
break
v2 += r
s1.send(v2)
s2.close()
s1.close()
[/python]
¿Qué hace? pues monta un servidor en el puerto 8080 y cada petición a este servidor la hace directamente al servidor remoto y la respuesta de este es lo que devuelve. Simple ¿verdad?.
Pero yo quiero modificar lo que me devuelve, así que hay que tratar la respuesta del servidor remoto:
[python]
#!/usr/bin/python
# proxy.py twitter.com 8080
import sys
import socket
import gzip
import StringIO
import re
pet = re.compile('(GET) (?P
js = re.compile('(\{.*\})')
def html(out):
ret = ''
ret += "Content-type: text/html\r\n"
ret += 'Content-Encoding: gzip\r\n'
ret += 'Content-Length: %s\r\n' % len(out)
ret += '\r\n'
ret += out
return ret
def compressBuf(buf):
zbuf = StringIO.StringIO()
zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf, compresslevel = 9)
zfile.write(buf)
zfile.close()
return html(zbuf.getvalue())
def json(cad, callback="json"):
if not callback:
return cad
else:
return "%s(%s);" % (callback, cad)
s = socket.socket()
mserver = 'localhost'
mport = 8080
s.bind((mserver, mport))
s.listen(10)
server = sys.argv[1]
port = int(sys.argv[2])
while True:
try:
print "waiting for requests at %s:%s" % (mserver, mport)
s1 = s.accept()
except KeyboardInterrupt:
s.close()
print "bye"
break
s2 = socket.socket()
s2.connect((server, port))
s1 = s1[0]
v = ''
r = 0
while not r or len(r) == 1024:
r = s1.recv(1024)
v += r
v = v.replace('Host: %s:%s' %(mserver, mport), 'Host: %s:%s' % (server, port))
callback=''
args = pet.search(v)
if args:
args = args.group('args').split('&')
for a in args:
c,val = a.split('=')
if c == 'callback':
callback = val
break
v = pet.sub(r'\1 \2 \4', v)
print v
s2.send(v)
v2 = ''
r = True
while r:
s2.settimeout(1)
try:
r = s2.recv(1024)
except:
break
v2 += r
v3 = v2.split('\r\n')
print v3
encoding = ''
for i in v3:
if i.startswith('Content-Encoding: '):
encoding = i[len('Content-Encoding: '):]
if encoding == 'gzip':
st = StringIO.StringIO(v3[-1])
content = gzip.GzipFile(fileobj=st).read()
else:
try:
content = js.search(v2).groups()[0]
except:
content = ''
print content
s1.send(json(content, callback))
s2.close()
s1.close()
[/python]
Puedes probar con twitter por ejemplo, lanzando el proxy "python proxy.py twitter.com 80" y haciendo la petición desde el navegador "http://localhost:8080/status/user_timeline/danigm.json?callback=jsonp123" o "http://localhost:8080/status/user_timeline/danigm.json?callback=holaaaaa".
Y jugando con esto puedes hacer un proxy que modifique toda respuesta, por lo que te puedes divertir mucho.
Por supuesto habrá muchos proxys http hechos en python, pero este lo he hecho yo, a bajo nivel y de mala manera :P




Recent comments