web-dev-qa-db-fra.com

Python urllib2, authentification HTTP de base et tr.im

Je m'amuse à essayer d'écrire du code pour utiliser les API tr.im afin de raccourcir une URL.

Après avoir lu http://docs.python.org/library/urllib2.html , j'ai essayé:

   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 est 200 (je pense que cela devrait être 202). L'URL est valide, mais l'authentification HTTP de base ne semble pas avoir fonctionné, car l'URL abrégée ne figure pas dans ma liste d'URL (à l'adresse http://tr.im/?page=1 ).

Après avoir lu http://www.voidspace.org.uk/python/articles/authentication.shtml#doing-it-properly j'ai aussi essayé:

   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()

Mais j'obtiens les mêmes résultats. (response.code est 200 et l'URL est valide, mais n'est pas enregistrée dans mon compte à l'adresse http://tr.im/ .)

Si j'utilise des paramètres de chaîne de requête au lieu d'une authentification HTTP de base, procédez comme suit:

   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()

... alors non seulement l'URL est valide, mais elle est enregistrée dans mon compte tr.im. (Bien que le code réponse soit toujours 200.)

Il doit y avoir quelque chose qui ne va pas avec mon code cependant (et pas avec l’API de tr.im), car

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

...résultats:

{"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"}

... et l'URL apparaît dans ma liste d'URL sur http://tr.im/?page=1 .

Et si je cours:

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

... encore une fois, je reçois:

{"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"}

Le code de note est 201 et le message est "URL tr.im déjà créée [yacitus]".

Je ne dois pas effectuer correctement l'authentification HTTP de base (dans l'une ou l'autre tentative). Pouvez-vous repérer mon problème? Peut-être que je devrais regarder et voir ce qui est envoyé sur le fil? Je n'ai jamais fait ça avant. Existe-t-il des API Python que je peux utiliser (peut-être dans pdb)? Ou existe-t-il un autre outil (de préférence pour Mac OS X) que je peux utiliser?

81
Daryl Spitzer

Cela semble très bien fonctionner (tiré d'un autre thread)

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)
243
Flowpoke

Solution vraiment pas chère:

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

(que vous pouvez décider de ne pas utiliser pour plusieurs raisons, comme la sécurité de l'URL)

exemple de l'API Github :

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

Jetez un oeil à this SO post réponse et regardez aussi ceci tutoriel sur l'authentification de base à partir de rllib2 manque de manuel =.

Pour que l'authentification de base urllib2 fonctionne, la réponse http doit contenir le code HTTP 401 non autorisé et une clé "WWW-Authenticate" Avec la valeur "Basic" Sinon, Python n'enverra pas vos informations de connexion et vous devrez soit utiliser Requests , ou urllib.urlopen(url) avec votre identifiant dans l'URL, ou ajoutez un en-tête comme dans @ Flowpoke'sréponse .

Vous pouvez visualiser votre erreur en plaçant votre urlopen dans un bloc try:

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

La méthode recommandée consiste à utiliser 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

Voici une source unique Python 2/3 compatible urllib2:

#!/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+ introduit HTTPPasswordMgrWithPriorAuth() qui permet:

..pour éliminer la gestion inutile des réponses 401 ou pour envoyer inconditionnellement des informations d'identification lors de la première demande afin de communiquer avec les serveurs qui renvoient une réponse 404 au lieu de 401 si l'en-tête Authorization n'est pas envoyé.

#!/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()

Il est facile de remplacer HTTPBasicAuthHandler() par ProxyBasicAuthHandler() si nécessaire dans ce cas.

6
jfs

Je suggérerais que la solution actuelle consiste à utiliser mon paquetage rllib2_prior_auth qui résout ce joli problème (je travaille sur inclusion à la bibliothèque standard.

4
mcepl

Mêmes solutions que problème d'authentification de base Python urllib2 s'applique.

voir https://stackoverflow.com/a/24048852/1733117 ; vous pouvez sous-classe urllib2.HTTPBasicAuthHandler pour ajouter l'en-tête Authorization à chaque demande correspondant à l'URL connue.

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
3
dnozay
0
Andrew G