309 lines
10 KiB
Python
309 lines
10 KiB
Python
#
|
|
# 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
|
|
import logging
|
|
|
|
|
|
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/'
|
|
self.logger = logging.getLogger('cmislib.net.RESTService')
|
|
|
|
def get(self,
|
|
url,
|
|
username=None,
|
|
password=None,
|
|
**kwargs):
|
|
|
|
""" Makes a get request to the URL specified."""
|
|
|
|
headers = None
|
|
if kwargs:
|
|
if 'headers' in kwargs:
|
|
headers = kwargs['headers']
|
|
del(kwargs['headers'])
|
|
self.logger.debug('Headers passed in:%s' % headers)
|
|
if url.find('?') >= 0:
|
|
url = url + '&' + urlencode(kwargs)
|
|
else:
|
|
url = url + '?' + urlencode(kwargs)
|
|
|
|
self.logger.debug('About to do a GET on:' + url)
|
|
|
|
request = RESTRequest(url, method='GET')
|
|
|
|
# add a user-agent
|
|
request.add_header('User-Agent', self.user_agent)
|
|
if headers:
|
|
for k, v in headers.items():
|
|
self.logger.debug('Adding header:%s:%s' % (k, v))
|
|
request.add_header(k, v)
|
|
|
|
# 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. """
|
|
|
|
headers = None
|
|
if kwargs:
|
|
if 'headers' in kwargs:
|
|
headers = kwargs['headers']
|
|
del(kwargs['headers'])
|
|
self.logger.debug('Headers passed in:%s' % headers)
|
|
if url.find('?') >= 0:
|
|
url = url + '&' + urlencode(kwargs)
|
|
else:
|
|
url = url + '?' + urlencode(kwargs)
|
|
|
|
self.logger.debug('About to do a DELETE on:' + url)
|
|
|
|
request = RESTRequest(url, method='DELETE')
|
|
|
|
# add a user-agent
|
|
request.add_header('User-Agent', self.user_agent)
|
|
if headers:
|
|
for k, v in headers.items():
|
|
self.logger.debug('Adding header:%s:%s' % (k, v))
|
|
request.add_header(k, v)
|
|
|
|
# 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.
|
|
"""
|
|
|
|
headers = None
|
|
if kwargs:
|
|
if 'headers' in kwargs:
|
|
headers = kwargs['headers']
|
|
del(kwargs['headers'])
|
|
self.logger.debug('Headers passed in:%s' % headers)
|
|
if url.find('?') >= 0:
|
|
url = url + '&' + urlencode(kwargs)
|
|
else:
|
|
url = url + '?' + urlencode(kwargs)
|
|
|
|
self.logger.debug('About to do a PUT on:' + url)
|
|
|
|
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)
|
|
if headers:
|
|
for k, v in headers.items():
|
|
self.logger.debug('Adding header:%s:%s' % (k, v))
|
|
request.add_header(k, v)
|
|
|
|
# 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.
|
|
"""
|
|
|
|
headers = None
|
|
if kwargs:
|
|
if 'headers' in kwargs:
|
|
headers = kwargs['headers']
|
|
del(kwargs['headers'])
|
|
self.logger.debug('Headers passed in:%s' % headers)
|
|
if url.find('?') >= 0:
|
|
url = url + '&' + urlencode(kwargs)
|
|
else:
|
|
url = url + '?' + urlencode(kwargs)
|
|
|
|
self.logger.debug('About to do a POST on:' + url)
|
|
|
|
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)
|
|
if headers:
|
|
for k, v in headers.items():
|
|
self.logger.debug('Adding header:%s:%s' % (k, v))
|
|
request.add_header(k, v)
|
|
|
|
# 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
|