Python urllib2, базовая HTTP-аутентификация и tr.im - PullRequest
79 голосов
/ 11 марта 2009

Я играю, пытаюсь написать какой-нибудь код для использования tr.im API для сокращения URL.

После прочтения http://docs.python.org/library/urllib2.html, Я попробовал:

   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 равен 200 (я думаю, это должно быть 202). URL действителен, но похоже, базовая HTTP-аутентификация не сработала, потому что сокращенный URL отсутствует в моем списке URL-адресов (на http://tr.im/?page=1).

После прочтения http://www.voidspace.org.uk/python/articles/authentication.shtml#doing-it-properly Я также попробовал:

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

Но я получаю те же результаты. (response.code равен 200, а URL действителен, но не записано в моем аккаунте на http://tr.im/.)

Если я использую параметры строки запроса вместо базовой HTTP-аутентификации, как это:

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

... тогда URL-адрес не только действителен, но и записан в моей учетной записи tr.im. (Хотя response.code все еще 200.)

Должно быть, что-то не так с моим кодом (а не с API tr.im), потому что

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

... возвращается:

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

... и URL появляется в моем списке URL-адресов на http://tr.im/?page=1.

А если я бегу:

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

... опять я получаю:

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

Код примечания - 201, а сообщение - «tr.im URL уже создан [yacitus].»

Я не должен выполнять базовую HTTP-аутентификацию правильно (ни при одной попытке). Можете ли вы определить мою проблему? Может быть, я должен посмотреть и посмотреть, что отправляется по проводам? Я никогда не делал этого раньше. Могу ли я использовать Python API (возможно, в pdb)? Или я могу использовать другой инструмент (предпочтительно для Mac OS X)?

Ответы [ 7 ]

240 голосов
/ 15 ноября 2010

Это, кажется, работает очень хорошо (взято из другого потока)

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)
19 голосов
/ 11 марта 2009

Действительно дешевое решение:

urllib.urlopen('http://user:xxxx@api.tr.im/api')

(который вы можете решить, не подходит по ряду причин, например, безопасность URL)

Пример Github API :

>>> import urllib, json
>>> result = urllib.urlopen('https://personal-access-token:x-oauth-basic@api.github.com/repos/:owner/:repo')
>>> r = json.load(result.fp)
>>> result.close()
13 голосов
/ 14 марта 2012

Взгляните на этот пост-ответ , а также посмотрите на базовое руководство по аутентификации из urllib2 отсутствующего руководства .

Чтобы базовая аутентификация urllib2 работала, ответ http должен содержать код HTTP 401 Unauthorized и ключ "WWW-Authenticate" со значением "Basic" в противном случае Python выиграл ' Вы не можете отправить свою регистрационную информацию, и вам нужно будет либо использовать Requests , либо urllib.urlopen(url) с вашим логином в URL, либо добавить заголовок, как в @ Flowpoke's ответить .

Вы можете просмотреть свою ошибку, поместив urlopen в блок try:

try:
    urllib2.urlopen(urllib2.Request(url))
except urllib2.HTTPError, e:
    print e.headers
    print e.headers.has_key('WWW-Authenticate')
6 голосов
/ 04 ноября 2015

Рекомендуемый способ заключается в использовании requests модуля :

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

Вот один исходный код, совместимый с Python 2/3 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+ представляет HTTPPasswordMgrWithPriorAuth(), что позволяет:

.. для устранения ненужной обработки ответа 401 или для безоговорочной отправки учетных данных по первому запросу для связи с серверами, которые возвращают ответ 404 вместо 401, если заголовок авторизации не отправлен.

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

В этом случае при необходимости легко заменить HTTPBasicAuthHandler() на ProxyBasicAuthHandler().

4 голосов
/ 11 сентября 2014

Я бы предположил, что текущим решением является использование моего пакета urllib2_prior_auth , который решает эту проблему довольно хорошо (я работаю над включением в стандартную библиотеку.

3 голосов
/ 05 июня 2014

Те же решения, что и Python urllib2 Основная проблема аутентификации применимо.

см. https://stackoverflow.com/a/24048852/1733117; вы можете создать подкласс urllib2.HTTPBasicAuthHandler, чтобы добавить заголовок Authorization к каждому запросу, который соответствует известному URL.

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 голосов
/ 11 ноября 2012
...