2009-03-11 31 views
73

Estoy jugando, intentando escribir algún código para usar las API tr.im para acortar una URL.Python urllib2, autenticación HTTP básica y tr.im

Después de leer http://docs.python.org/library/urllib2.html, he intentado:

TRIM_API_URL = 'http://api.tr.im/api' 
    auth_handler = urllib2.HTTPBasicAuthHandler() 
    auth_handler.add_password(realm='tr.im', 
          uri=TRIM_API_URL, 
          user=USERNAME, 
          passwd=PASSWORD) 
    opener = urllib2.build_opener(auth_handler) 
    urllib2.install_opener(opener) 
    response = urllib2.urlopen('%s/trim_simple?url=%s' 
           % (TRIM_API_URL, url_to_trim)) 
    url = response.read().strip() 

response.code es de 200 (creo que debe ser 202). url es válido, pero la autenticación HTTP básica no parece haber funcionado, porque la URL acortada no está en mi lista de URL (en http://tr.im/?page=1).

Después de leer http://www.voidspace.org.uk/python/articles/authentication.shtml#doing-it-properly También probé:

TRIM_API_URL = 'api.tr.im/api' 
    password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() 
    password_mgr.add_password(None, TRIM_API_URL, USERNAME, PASSWORD) 
    auth_handler = urllib2.HTTPBasicAuthHandler(password_mgr) 
    opener = urllib2.build_opener(auth_handler) 
    urllib2.install_opener(opener) 
    response = urllib2.urlopen('http://%s/trim_simple?url=%s' 
           % (TRIM_API_URL, url_to_trim)) 
    url = response.read().strip() 

Pero consigo los mismos resultados. (Response.code es de 200 y el URL es válida, pero no registrado en mi cuenta en http://tr.im/.)

Si utilizo los parámetros de consulta de cadenas en lugar de la autenticación básica HTTP, como esto:

TRIM_API_URL = 'http://api.tr.im/api' 
    response = urllib2.urlopen('%s/trim_simple?url=%s&username=%s&password=%s' 
           % (TRIM_API_URL, 
           url_to_trim, 
           USERNAME, 
           PASSWORD)) 
    url = response.read().strip() 

. ..then no solo es válida la URL, sino que está registrada en mi cuenta tr.im. (Aunque response.code sigue siendo 200.)

Debe haber algo malo en mi código, aunque (y no de API Tr.im), ya

$ curl -u yacitus:xxxx http://api.tr.im/api/trim_url.json?url=http://www.google.co.uk 

... devuelve:

{"trimpath":"hfhb","reference":"nH45bftZDWOX0QpVojeDbOvPDnaRaJ","trimmed":"11\/03\/2009","destination":"http:\/\/www.google.co.uk\/","trim_path":"hfhb","domain":"google.co.uk","url":"http:\/\/tr.im\/hfhb","visits":0,"status":{"result":"OK","code":"200","message":"tr.im URL Added."},"date_time":"2009-03-11T10:15:35-04:00"} 

... y la URL aparece en mi lista de URL en http://tr.im/?page=1.

Y si me quedo:

$ curl -u yacitus:xxxx http://api.tr.im/api/trim_url.json?url=http://www.google.co.uk 

... otra vez, me sale:

{"trimpath":"hfhb","reference":"nH45bftZDWOX0QpVojeDbOvPDnaRaJ","trimmed":"11\/03\/2009","destination":"http:\/\/www.google.co.uk\/","trim_path":"hfhb","domain":"google.co.uk","url":"http:\/\/tr.im\/hfhb","visits":0,"status":{"result":"OK","code":"201","message":"tr.im URL Already Created [yacitus]."},"date_time":"2009-03-11T10:15:35-04:00"} 

código Note es 201, y el mensaje es "URL tr.im ya creados [yacitus]. "

No debo estar haciendo la autenticación HTTP básica correctamente (en cualquier intento). ¿Puedes ver mi problema? Tal vez debería mirar y ver lo que se envía por el cable? Nunca he hecho eso antes. ¿Hay API de Python que pueda usar (quizás en pdb)? ¿O hay otra herramienta (preferiblemente para Mac OS X) que pueda usar?

+2

el sitio debe devolver '" WWW-Authenticate "' y el código 401 antes de que urllib2 (o httplib2) envíe sus credenciales. [Ver mi respuesta a continuación] (http://stackoverflow.com/questions/635113/python-urllib2-basic-http-authentication-and-tr-im/9698319#9698319). –

+0

Nota: Este servicio parece estar extinto. – Laurel

Respuesta

229

Esto parece para trabajar realmente bien (tomado de otro hilo)

import urllib2, base64 

request = urllib2.Request("http://api.foursquare.com/v1/user") 
base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '') 
request.add_header("Authorization", "Basic %s" % base64string) 
result = urllib2.urlopen(request) 
+3

[ fuente] (http://stackoverflow.com/a/2955687/708764) – seler

+7

En lugar de base64.encodestring y reemplazar, uso base64.standard_b64encode –

+5

'request.add_header ('autorización', b'Basic '+ base64.b64encode (nombre de usuario + b ':' + contraseña)) ' – jfs

18

solución muy barata:

urllib.urlopen('http://user:[email protected]/api') 

(el cual puede decidir no es adecuada para una serie de razones, como la seguridad de la url)

Github API example:

>>> import urllib, json 
>>> result = urllib.urlopen('https://personal-access-token:[email protected]/repos/:owner/:repo') 
>>> r = json.load(result.fp) 
>>> result.close() 
+0

¿Hay alguna ventaja en esto sobre el uso de parámetros de cadena de consulta? –

+0

Daryl: si funciona, diría que es una ventaja, sí, y probablemente más segura que los argumentos de cadena de consulta, ya que la mayoría de los clientes http son un poco más cuidadosos con la forma en que los manejan. –

+0

Probablemente voy a ir con esto (por lo que obtienes mi voto popular), pero aún me gustaría descubrir qué le pasa a mi código (así que esta no será mi respuesta aceptada). –

12

Eche un vistazo a this SO post answer y también vea este basic authentication tutorial del urllib2 missing manual.

Para que urllib2 autenticación básica para trabajar, la respuesta HTTP debe contener código HTTP 401 no autorizado y una clave "WWW-Authenticate" con el valor "Basic" lo contrario, Python no enviará su información de inicio de sesión y tendrá que utilice Requests, o urllib.urlopen(url) con su inicio de sesión en la url, o agregue un encabezado como en @Flowpoke'sanswer.

Puede ver su error poniendo su urlopen en un bloque try:

try: 
    urllib2.urlopen(urllib2.Request(url)) 
except urllib2.HTTPError, e: 
    print e.headers 
    print e.headers.has_key('WWW-Authenticate') 
+0

Esto me ayudó porque imprimir los encabezados me llevó a darme cuenta de que había tipeado el reino de la autenticación. +1 – freespace

2

mismas soluciones que se aplican Python urllib2 Basic Auth Problem.

ver https://stackoverflow.com/a/24048852/1733117; Puede crear la subclase urllib2.HTTPBasicAuthHandler para agregar el encabezado Authorization a cada solicitud que coincida con la URL conocida.

class PreemptiveBasicAuthHandler(urllib2.HTTPBasicAuthHandler): 
    '''Preemptive basic auth. 

    Instead of waiting for a 403 to then retry with the credentials, 
    send the credentials if the url is handled by the password manager. 
    Note: please use realm=None when calling add_password.''' 
    def http_request(self, req): 
     url = req.get_full_url() 
     realm = None 
     # this is very similar to the code from retry_http_basic_auth() 
     # but returns a request object. 
     user, pw = self.passwd.find_user_password(realm, url) 
     if pw: 
      raw = "%s:%s" % (user, pw) 
      auth = 'Basic %s' % base64.b64encode(raw).strip() 
      req.add_unredirected_header(self.auth_header, auth) 
     return req 

    https_request = http_request 
+0

¿No es redundante la llamada a 'strip' después de' b64encode'? –

2

Yo sugeriría que la solución actual es utilizar mi paquete urllib2_prior_auth que resuelve esta bastante bien (yo trabajo en inclusion a la lib estándar

6

The recommended way es utilizar requests module:.

#!/usr/bin/env python 
import requests # $ python -m pip install requests 
####from pip._vendor import requests # bundled with python 

url = 'https://httpbin.org/hidden-basic-auth/user/passwd' 
user, password = 'user', 'passwd' 

r = requests.get(url, auth=(user, password)) # send auth unconditionally 
r.raise_for_status() # raise an exception if the authentication fails 

Aquí hay una fuente única compatible con Python 2/3 urllib2 -variante basada en:

#!/usr/bin/env python 
import base64 
try: 
    from urllib.request import Request, urlopen 
except ImportError: # Python 2 
    from urllib2 import Request, urlopen 

credentials = '{user}:{password}'.format(**vars()).encode() 
urlopen(Request(url, headers={'Authorization': # send auth unconditionally 
    b'Basic ' + base64.b64encode(credentials)})).close() 

Python 3.5+ introduces HTTPPasswordMgrWithPriorAuth() que permite:

..para eliminar innecesaria la gestión de respuesta 401, o para enviar incondicionalmente credenciales en la primera solicitud con el fin de comunicarse con los servidores que devuelven una respuesta en lugar de un 401 si la autorización 404 cabecera no se envía ..

#!/usr/bin/env python3 
import urllib.request as urllib2 

password_manager = urllib2.HTTPPasswordMgrWithPriorAuth() 
password_manager.add_password(None, url, user, password, 
           is_authenticated=True) # to handle 404 variant 
auth_manager = urllib2.HTTPBasicAuthHandler(password_manager) 
opener = urllib2.build_opener(auth_manager) 

opener.open(url).close() 

es fácil de reemplazar HTTPBasicAuthHandler() con ProxyBasicAuthHandler() si es necesario en este caso.

Cuestiones relacionadas