blob: 39cd168e349831232f9e24652a4b5bfc2b4b73db [file] [log] [blame]
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
'''
Module that knows how to connect to the AtomPub Binding of a CMIS repo
'''
from urllib import urlencode
from urllib2 import HTTPBasicAuthHandler, \
HTTPPasswordMgrWithDefaultRealm, \
HTTPRedirectHandler, \
HTTPDefaultErrorHandler, \
HTTPError, \
Request, \
build_opener, \
AbstractBasicAuthHandler
class SmartRedirectHandler(HTTPRedirectHandler):
""" Handles 301 and 302 redirects """
def http_error_301(self, req, fp, code, msg, headers):
""" Handle a 301 error """
result = HTTPRedirectHandler.http_error_301(
self, req, fp, code, msg, headers)
result.status = code
return result
def http_error_302(self, req, fp, code, msg, headers):
""" Handle a 302 error """
result = HTTPRedirectHandler.http_error_302(
self, req, fp, code, msg, headers)
result.status = code
return result
class DefaultErrorHandler(HTTPDefaultErrorHandler):
""" Default error handler """
def http_error_default(self, req, fp, code, msg, headers):
"""Provide an implementation for the default handler"""
result = HTTPError(
req.get_full_url(), code, msg, headers, fp)
result.status = code
return result
class ContextualBasicAuthHandler(HTTPBasicAuthHandler):
"""
Handles 401 errors without recursing indefinitely. The recursing
behaviour has been introduced in Python 2.6.5 to handle 401 redirects
used by some architectures of authentication.
"""
def __init__(self, password_mgr):
HTTPBasicAuthHandler.__init__(self, password_mgr)
self.authContext = set([])
def http_error_401(self, req, fp, code, msg, headers):
"""Override the default autoretry behaviour"""
url = req.get_full_url()
hdrs = req.header_items()
hdrs = ', '.join(['%s: %s' % (key, value)
for key, value in sorted(hdrs)])
context = (url, hdrs)
if context in self.authContext:
self.authContext.clear()
result = HTTPError(
req.get_full_url(), code, msg, headers, fp)
result.status = code
return result
self.authContext.add(context)
return self.http_error_auth_reqed('www-authenticate',
url, req, headers)
class RESTService(object):
"""
Generic service for interacting with an HTTP end point. Sets headers
such as the USER_AGENT and builds the basic auth handler.
"""
def __init__(self):
self.user_agent = 'cmislib/%s +http://chemistry.apache.org/'
def get(self,
url,
username=None,
password=None,
**kwargs):
""" Makes a get request to the URL specified."""
if kwargs:
if url.find('?') >= 0:
url = url + '&' + urlencode(kwargs)
else:
url = url + '?' + urlencode(kwargs)
request = RESTRequest(url, method='GET')
# add a user-agent
request.add_header('User-Agent', self.user_agent)
# create a password manager
passwordManager = HTTPPasswordMgrWithDefaultRealm()
passwordManager.add_password(None, url, username, password)
opener = build_opener(SmartRedirectHandler(),
DefaultErrorHandler(),
ContextualBasicAuthHandler(passwordManager))
return opener.open(request)
def delete(self, url, username=None, password=None, **kwargs):
""" Makes a delete request to the URL specified. """
if kwargs:
if url.find('?') >= 0:
url = url + '&' + urlencode(kwargs)
else:
url = url + '?' + urlencode(kwargs)
request = RESTRequest(url, method='DELETE')
# add a user-agent
request.add_header('User-Agent', self.user_agent)
# create a password manager
passwordManager = HTTPPasswordMgrWithDefaultRealm()
passwordManager.add_password(None, url, username, password)
opener = build_opener(SmartRedirectHandler(),
DefaultErrorHandler(),
ContextualBasicAuthHandler(passwordManager))
#try:
# opener.open(request)
#except urllib2.HTTPError, e:
# if e.code is not 204:
# raise e
#return None
return opener.open(request)
def put(self,
url,
payload,
contentType,
username=None,
password=None,
**kwargs):
"""
Makes a PUT request to the URL specified and includes the payload
that gets passed in. The content type header gets set to the
specified content type.
"""
if kwargs:
if url.find('?') >= 0:
url = url + '&' + urlencode(kwargs)
else:
url = url + '?' + urlencode(kwargs)
request = RESTRequest(url, payload, method='PUT')
# set the content type header
request.add_header('Content-Type', contentType)
# add a user-agent
request.add_header('User-Agent', self.user_agent)
# create a password manager
passwordManager = HTTPPasswordMgrWithDefaultRealm()
passwordManager.add_password(None, url, username, password)
opener = build_opener(SmartRedirectHandler(),
DefaultErrorHandler(),
ContextualBasicAuthHandler(passwordManager))
return opener.open(request)
def post(self,
url,
payload,
contentType,
username=None,
password=None,
**kwargs):
"""
Makes a POST request to the URL specified and posts the payload
that gets passed in. The content type header gets set to the
specified content type.
"""
if kwargs:
if url.find('?') >= 0:
url = url + '&' + urlencode(kwargs)
else:
url = url + '?' + urlencode(kwargs)
request = RESTRequest(url, payload, method='POST')
# set the content type header
request.add_header('Content-Type', contentType)
# add a user-agent
request.add_header('User-Agent', self.user_agent)
# create a password manager
passwordManager = HTTPPasswordMgrWithDefaultRealm()
passwordManager.add_password(None, url, username, password)
opener = build_opener(SmartRedirectHandler(),
DefaultErrorHandler(),
ContextualBasicAuthHandler(passwordManager))
try:
return opener.open(request)
except HTTPError, e:
if e.code is not 201:
return e
else:
return e.read()
class RESTRequest(Request):
"""
Overrides urllib's request default behavior
"""
def __init__(self, *args, **kwargs):
""" Constructor """
self._method = kwargs.pop('method', 'GET')
assert self._method in ['GET', 'POST', 'PUT', 'DELETE']
Request.__init__(self, *args, **kwargs)
def get_method(self):
""" Override the get method """
return self._method