Ok, I have prepared a more thorough implementation of this idea.  Although
some unit tests were failing for me (looks like some servers have been
updated/reconfigured since the tests were last updated) I have verified that
this patch doesn't break any tests that were already passing, and that it
works properly in GeoNode (ie no more session proliferation).

I don't believe I have an account on the OWSLib trac and self-registration
does not seem to be enabled, but the patch is attached to this email (I can
make it available via HTTP as well if that would be more useful; it wasn't
clear if my last attachment to this list made it through or not.)

There's a lot changed internally (everything is standardized on going
through local "opener" variables now) but for the factory methods used there
are just a few new optional arguments when setting up the client object
(these should work for WebFeatureService, WebMapService, WebCoverageService,
and CatalogueServiceWeb):

*opener* = a urllib2.OpenerDirector.  if the other parameters listed here
are provided then they will be added to the opener.

*cookies* = a cookielib.CookieJar implementation (compatibility note! where
cookies were allowed before they were expected as a literal string, I have
used a CookieJar as this is the only option to work with the builtin
urllib2.HTTPCookieProcessor.  However, when I tried to test the previous
cookie code I couldn't get it to work due to an apparent bug in the code so
I would be surprised if client code is relying on that functionality.  I can
elaborate more on the bug I think I found but that code is replaced in the
patch.)

*username* = a username for http basic authentication (only used if a
password is also provided)
*password* = a password for http basic authentication (only used if a
username is also provided)

--
David Winslow
OpenGeo - http://opengeo.org/

On Wed, Apr 27, 2011 at 10:15 AM, David Winslow <[email protected]>wrote:

> The patch I have does not modify the existing password support - so when
> you pass in username/password to the openURL function, a new OpenerDirector
> is created, configured and used for that one request and immediately thrown
> away, ignoring the new "opener" field I have added to the various OWS client
> classes.  I'll try and unify these for the final patch, now that I've gotten
> some feedback on the approach I've chosen.
>
> --
> David Winslow
> OpenGeo - http://opengeo.org/
>
>
> On Tue, Apr 26, 2011 at 10:45 AM, Dominic Lowe <[email protected]>wrote:
>
>> Hi David,
>>
>> I'd be very happy to see that patch - I'm sure there are other
>> circumstances when cookies would be useful in OWSLib.
>>
>> Am I right in thinking your current patch does not support http
>> password/username logon? I think that's pretty important to maintain as that
>> was a requirement from some other users.
>>
>> Otherwise I'm pretty happy for you to suggest a patch. Tom Kralidis may
>> have more comments as he has been most active on OWSLib recently.
>>
>> Regards,
>> Dominic
>>
>>
>>
>> On 25/04/11 19:07, [email protected] wrote:
>>
>>> Hi all,
>>>
>>> I work on GeoNode (http://geonode.org/).  We're using OWSLib to fetch
>>> data from GeoNetwork.  Recently I've discovered that due to the way
>>> GeoNetwork handles sessions, it makes a big difference for us to include
>>> HTTP cookies on our service requests (without them, GeoNetwork creates a new
>>> session for each service request and eventually locks up after running out
>>> of RAM.)
>>>
>>> Therefore I would like to modify owslib to use a (persistent) instance of
>>> urllib2.HTTPCookieProcessor when opening URLs, one which I can get a
>>> reference to so that I can log in to GeoNetwork and have owslib use the
>>> session cookie.  Ideally, there would be a way to provide my own instance of
>>> urllib2.OpenerDirector which I can customize as needed.
>>>
>>> I am currently using a monkey patch to fix this behavior, but it's not
>>> the way I'd like to do things (you can see the diff in GeoNode here:
>>> https://github.com/dwins/geonode/compare/master...csw-cookies ).
>>>
>>> For a deeper fix I see basically 3 options:
>>>
>>> 1) Use Python's urllib2.install_opener to set a default opener instance
>>> to use, and be careful not to pass arguments into owslib methods that would
>>> cause them to create their own openers (for example, including a username
>>> and password in a call to owslib.util.openURL).
>>> 2) Create a module-level variable (owslib.util.opener) and modify
>>> owslib.util.openURL and owslib.util.http_post to reference it exclusively.
>>>  Then client code which needs to modify the HTTP handling behavior can
>>> simply replace this variable.
>>> 3) Make each client class (ie, owslib.csw.CatalogueServiceWeb) use its
>>> own opener instance which may be provided by client code.
>>>
>>> The attached patch is a partial implementation of approach #3. (In each
>>> of these cases there is the question of how to handle the username/password
>>> arguments to owslib.util.openURL, I haven't addressed this.)
>>>
>>> Is this sort of thing something the OWSLib community would be interested
>>> in incorporating?  If so I'm happy to update this patch to incorporate
>>> feedback etc.
>>>
>>> --
>>> David Winslow
>>> OpenGeo - http://opengeo.org/
>>>
>>>
>> --
>> Scanned by iCritical.
>> _______________________________________________
>> Community mailing list
>> [email protected]
>> http://lists.gispython.org/mailman/listinfo/community
>>
>
>
diff --git a/owslib/coverage/wcs100.py b/owslib/coverage/wcs100.py
index a0fd70b..fdb2d5c 100644
--- a/owslib/coverage/wcs100.py
+++ b/owslib/coverage/wcs100.py
@@ -11,7 +11,7 @@
 
 from wcsBase import WCSBase, WCSCapabilitiesReader
 from urllib import urlencode
-from owslib.util import openURL, testXMLValue
+from owslib.util import testXMLValue
 from owslib.etree import etree
 import os, errno
 
@@ -34,7 +34,8 @@ class WebCoverageService_1_0_0(WCSBase):
         else:
             raise KeyError, "No content named %s" % name
     
-    def __init__(self,url,xml, cookies):
+    def __init__(self,url,xml, opener=None, cookies=None, username=None, password=None, url_base=None):
+        super(WebCoverageService_1_0_0, self).__init__(opener, cookies, username, password, url_base)
         self.version='1.0.0'
         self.url = url   
         self.cookies=cookies
@@ -135,9 +136,7 @@ class WebCoverageService_1_0_0(WCSBase):
         #encode and request
         data = urlencode(request)
         self.log.debug('WCS 1.0.0 DEBUG: Second part of URL: %s'%data)
-        
-        
-        u=openURL(base_url, data, method, self.cookies)
+        u=self.openURL(base_url, data, method)
 
         return u
                
diff --git a/owslib/coverage/wcs110.py b/owslib/coverage/wcs110.py
index d69ed6c..7edcddc 100644
--- a/owslib/coverage/wcs110.py
+++ b/owslib/coverage/wcs110.py
@@ -12,9 +12,8 @@
 ##########NOTE: Does not conform to new interfaces yet #################
 
 from wcsBase import WCSBase, WCSCapabilitiesReader
-from owslib.util import openURL, testXMLValue
+from owslib.util import testXMLValue
 from urllib import urlencode
-from urllib2 import urlopen
 from owslib.etree import etree
 import os, errno
 from owslib.coverage import wcsdecoder
@@ -169,7 +168,7 @@ class WebCoverageService_1_1_0(WCSBase):
         #encode and request
         data = urlencode(request)
         
-        u=openURL(base_url, data, method, self.cookies)
+        u=self.openURL(base_url, data, method)
         return u
         
         
diff --git a/owslib/coverage/wcsBase.py b/owslib/coverage/wcsBase.py
index 147d1ab..2793202 100644
--- a/owslib/coverage/wcsBase.py
+++ b/owslib/coverage/wcsBase.py
@@ -10,8 +10,9 @@
 # =============================================================================
 
 from urllib import urlencode
-from urllib2 import urlopen, Request
+from urllib2 import Request
 from owslib.etree import etree
+from owslib.util import Client
 import cgi
 from StringIO import StringIO
 
@@ -19,9 +20,9 @@ from StringIO import StringIO
 
 import logging
 
-class WCSBase(object):
+class WCSBase(Client):
     """Base class to be subclassed by version dependent WCS classes. Provides 'high-level' version independent methods"""
-    def __new__(self,url, xml, cookies):
+    def __new__(self, url, xml, opener, cookies, username, password):
         """ overridden __new__ method 
         
         @type url: string
@@ -31,7 +32,7 @@ class WCSBase(object):
         @return: inititalised WCSBase object
         """
         obj=object.__new__(self)
-        obj.__init__(url, xml, cookies)
+        obj.__init__(url, xml, opener, cookies, username, password)
         self.cookies=cookies
         self.log = logging.getLogger(__name__)
         consoleh  = logging.StreamHandler()
@@ -39,8 +40,8 @@ class WCSBase(object):
         self._describeCoverage = {} #cache for DescribeCoverage responses
         return obj
     
-    def __init__(self):
-        pass    
+    def __init__(self, url, opener, cookies, username, password):
+        super(WCSBase, self).__init__(opener, cookies, username, password, url)
 
     def getDescribeCoverage(self, identifier):
         ''' returns a describe coverage document - checks the internal cache to see if it has been fetched before '''
@@ -63,15 +64,16 @@ class WCSBase(object):
         elif level=='CRITICAL':
             self.log.setLevel(logging.CRITICAL)
         
-class WCSCapabilitiesReader(object):
+class WCSCapabilitiesReader(Client):
     """Read and parses WCS capabilities document into a lxml.etree infoset
     """
 
-    def __init__(self, version=None, cookies = None):
+    def __init__(self, version=None, cookies=None, opener=None, username=None, password=None, url_base=None):
         """Initialize
         @type version: string
         @param version: WCS Version parameter e.g '1.0.0'
         """
+        super(WCSCapabilitiesReader, self).__init__(opener, cookies, username, password, url_base)
         self.version = version
         self._infoset = None
         self.cookies = cookies
@@ -109,11 +111,7 @@ class WCSCapabilitiesReader(object):
         @rtype: elementtree tree
         @return: An elementtree tree representation of the capabilities document
         """
-        request = self.capabilities_url(service_url)
-        req = Request(request)
-        if self.cookies is not None:
-            req.add_header('Cookie', self.cookies)   
-        u = urlopen(req)
+        u = self.openURL(self.capabilities_url(service_url), '')
         return etree.fromstring(u.read())
     
     def readString(self, st):
@@ -125,15 +123,16 @@ class WCSCapabilitiesReader(object):
             raise ValueError("String must be of type string, not %s" % type(st))
         return etree.fromstring(st)
 
-class DescribeCoverageReader(object):
+class DescribeCoverageReader(Client):
     """Read and parses WCS DescribeCoverage document into a lxml.etree infoset
     """
 
-    def __init__(self, version, identifier, cookies):
+    def __init__(self, version, identifier, cookies, opener=None, username=None, password=None, url_base=None):
         """Initialize
         @type version: string
         @param version: WCS Version parameter e.g '1.0.0'
         """
+        super(DescribeCoverageReader, self).__init__(opener, cookies, username, password, url_base)
         self.version = version
         self._infoset = None
         self.identifier=identifier
@@ -182,11 +181,5 @@ class DescribeCoverageReader(object):
         @rtype: elementtree tree
         @return: An elementtree tree representation of the capabilities document
         """
-        request = self.descCov_url(service_url)
-        req = Request(request)
-        if self.cookies is not None:
-            req.add_header('Cookie', self.cookies)   
-        u = urlopen(req)
+        u = self.opener.open(self.descCov_url(service_url))
         return etree.fromstring(u.read())
-    
-       
diff --git a/owslib/csw.py b/owslib/csw.py
index 213f049..8474cd9 100644
--- a/owslib/csw.py
+++ b/owslib/csw.py
@@ -15,6 +15,7 @@ import random
 from owslib.etree import etree
 from owslib.filter import *
 from owslib import util
+from owslib.util import Client
 from owslib.ows import *
 from owslib.iso import *
 from owslib.fgdc import *
@@ -45,9 +46,9 @@ namespaces = {
 
 schema_location = '%s %s' % (namespaces['csw'], schema)
 
-class CatalogueServiceWeb:
+class CatalogueServiceWeb(Client):
     """ csw request class """
-    def __init__(self, url, lang='en-US', version='2.0.2', timeout=10):
+    def __init__(self, url, lang='en-US', version='2.0.2', timeout=10, opener=None, cookies=None, username=None, password=None):
         """
 
         Construct and process a GetCapabilities request
@@ -61,6 +62,7 @@ class CatalogueServiceWeb:
         - timeout: timeout in seconds
 
         """
+        super(CatalogueServiceWeb, self).__init__(opener, cookies, username, password, url)
 
         self.url = url
         self.lang = lang
@@ -454,7 +456,7 @@ class CatalogueServiceWeb:
 
     def _invoke(self):
         # do HTTP request
-        self.response = util.http_post(self.url, self.request, self.lang, self.timeout)
+        self.response = self.http_post(self.url, self.request, self.lang, self.timeout)
 
         # parse result see if it's XML
         self._exml = etree.parse(StringIO.StringIO(self.response))
diff --git a/owslib/feature/wfs100.py b/owslib/feature/wfs100.py
index 952d604..354432c 100644
--- a/owslib/feature/wfs100.py
+++ b/owslib/feature/wfs100.py
@@ -8,8 +8,8 @@
 
 import cgi
 from cStringIO import StringIO
+from owslib.util import Client
 from urllib import urlencode
-from urllib2 import urlopen
 import logging
 
 from owslib.etree import etree
@@ -43,12 +43,12 @@ class ServiceException(Exception):
     pass
 
 
-class WebFeatureService_1_0_0(object):
+class WebFeatureService_1_0_0(Client):
     """Abstraction for OGC Web Feature Service (WFS).
 
     Implements IWebFeatureService.
     """
-    def __new__(self,url, version, xml):
+    def __new__(self,url, version, xml, opener, cookies, username, password):
         """ overridden __new__ method 
         
         @type url: string
@@ -58,7 +58,7 @@ class WebFeatureService_1_0_0(object):
         @return: initialized WebFeatureService_1_0_0 object
         """
         obj=object.__new__(self)
-        obj.__init__(url, version, xml)
+        obj.__init__(url, version, xml, opener, cookies, username, password)
         self.log = logging.getLogger()
         consoleh  = logging.StreamHandler()
         self.log.addHandler(consoleh)    
@@ -72,8 +72,9 @@ class WebFeatureService_1_0_0(object):
             raise KeyError, "No content named %s" % name
     
     
-    def __init__(self, url, version, xml=None):
+    def __init__(self, url, version, xml, opener, cookies, username, password):
         """Initialize."""
+        super(WebFeatureService_1_0_0, self).__init__(opener, cookies, username, password, url)
         self.url = url
         self.version = version
         self._capabilities = None
@@ -117,8 +118,8 @@ class WebFeatureService_1_0_0(object):
         """Request and return capabilities document from the WFS as a 
         file-like object.
         NOTE: this is effectively redundant now"""
-        reader = WFSCapabilitiesReader(self.version)
-        return urlopen(reader.capabilities_url(self.url))
+        reader = WFSCapabilitiesReader(self.version, self.opener)
+        return self.opener.open(reader.capabilities_url(self.url))
     
     def items(self):
         '''supports dict-like items() access'''
@@ -183,9 +184,9 @@ class WebFeatureService_1_0_0(object):
         data = urlencode(request)
 
         if method == 'Post':
-            u = urlopen(base_url, data=data)
+            u = self.opener.open(base_url, data=data)
         else:
-            u = urlopen(base_url + data)
+            u = self.opener.open(base_url + data)
         
         # check for service exceptions, rewrap, and return
         # We're going to assume that anything with a content-length > 32k
@@ -363,12 +364,13 @@ class OperationMetadata:
         self.methods = dict(methods)
 
 
-class WFSCapabilitiesReader(object):
+class WFSCapabilitiesReader(Client):
     """Read and parse capabilities document into a lxml.etree infoset
     """
 
-    def __init__(self, version='1.0'):
+    def __init__(self, version='1.0', opener=None):
         """Initialize"""
+        super(WFSCapabilitiesReader, self).__init__(opener)
         self.version = version
         self._infoset = None
 
@@ -401,7 +403,7 @@ class WFSCapabilitiesReader(object):
             The URL to the WFS capabilities document.
         """
         request = self.capabilities_url(url)
-        u = urlopen(request)
+        u = self.opener.open(request)
         return etree.fromstring(u.read())
 
     def readString(self, st):
diff --git a/owslib/feature/wfs200.py b/owslib/feature/wfs200.py
index 70b1bda..bdf2dd9 100644
--- a/owslib/feature/wfs200.py
+++ b/owslib/feature/wfs200.py
@@ -9,13 +9,12 @@
 #owslib imports:
 from owslib.ows import ServiceIdentification, ServiceProvider, OperationsMetadata
 from owslib.etree import etree
-from owslib.util import nspath, testXMLValue
+from owslib.util import nspath, testXMLValue, Client
 
 #other imports
 import cgi
 from cStringIO import StringIO
 from urllib import urlencode
-from urllib2 import urlopen
 
 import logging
 
@@ -43,12 +42,12 @@ class ServiceException(Exception):
     pass
 
 
-class WebFeatureService_2_0_0(object):
+class WebFeatureService_2_0_0(Client):
     """Abstraction for OGC Web Feature Service (WFS).
 
     Implements IWebFeatureService.
     """
-    def __new__(self,url, version, xml):
+    def __new__(self,url, version, xml, opener, cookies, username, password):
         """ overridden __new__ method 
         
         @type url: string
@@ -58,7 +57,7 @@ class WebFeatureService_2_0_0(object):
         @return: initialized WebFeatureService_2_0_0 object
         """
         obj=object.__new__(self)
-        obj.__init__(url, version, xml)
+        obj.__init__(url, version, xml, opener, cookies, username, password)
         self.log = logging.getLogger()
         consoleh  = logging.StreamHandler()
         self.log.addHandler(consoleh)    
@@ -72,13 +71,14 @@ class WebFeatureService_2_0_0(object):
             raise KeyError, "No content named %s" % name
     
     
-    def __init__(self, url,  version, xml=None):
+    def __init__(self, url, version, xml, opener, cookies, username, password):
         """Initialize."""
+        super(WebFeatureService_2_0_0, self).__init__(opener, cookies, username, password)
         log.debug('building WFS %s'%url)
         self.url = url
         self.version = version
         self._capabilities = None
-        reader = WFSCapabilitiesReader(self.version)
+        reader = WFSCapabilitiesReader(self.version, self.opener)
         if xml:
             self._capabilities = reader.readString(xml)
         else:
@@ -132,7 +132,7 @@ class WebFeatureService_2_0_0(object):
         file-like object.
         NOTE: this is effectively redundant now"""
         reader = WFSCapabilitiesReader(self.version)
-        return urlopen(reader.capabilities_url(self.url))
+        return self.opener.open(reader.capabilities_url(self.url))
     
     def items(self):
         '''supports dict-like items() access'''
@@ -200,9 +200,9 @@ class WebFeatureService_2_0_0(object):
         data = urlencode(request)
 
         if method == 'Post':
-            u = urlopen(base_url, data=data)
+            u = self.opener.open(base_url, data=data)
         else:
-            u = urlopen(base_url + data)
+            u = self.opener.open(base_url + data)
         
         # check for service exceptions, rewrap, and return
         # We're going to assume that anything with a content-length > 32k
@@ -245,7 +245,7 @@ class WebFeatureService_2_0_0(object):
             for kw in kwargs.keys():
                 request[kw]=str(kwargs[kw])
         data=urlencode(request)
-        u = urlopen(base_url + data)
+        u = self.opener.open(base_url + data)
         return u.read()
         
         
@@ -260,7 +260,7 @@ class WebFeatureService_2_0_0(object):
         base_url = self.getOperationByName('ListStoredQueries').methods[method]['url']
         request = {'service': 'WFS', 'version': self.version, 'request': 'ListStoredQueries'}
         data = urlencode(request)
-        u = urlopen(base_url + data)
+        u = self.opener.open(base_url + data)
         tree=etree.fromstring(u.read())
         base_url = self.getOperationByName('ListStoredQueries').methods[method]['url']
         tempdict={}       
@@ -278,7 +278,7 @@ class WebFeatureService_2_0_0(object):
         base_url = self.getOperationByName('DescribeStoredQueries').methods[method]['url']
         request = {'service': 'WFS', 'version': self.version, 'request': 'DescribeStoredQueries'}
         data = urlencode(request)
-        u = urlopen(base_url + data)
+        u = self.opener.open(base_url + data)
         tree=etree.fromstring(u.read())
         tempdict2={} 
         for sqelem in tree[:]:
@@ -360,12 +360,13 @@ class ContentMetadata:
         self.styles=None
         self.timepositions=None
 
-class WFSCapabilitiesReader(object):
+class WFSCapabilitiesReader(Client):
     """Read and parse capabilities document into a lxml.etree infoset
     """
 
-    def __init__(self, version='2.0.0'):
+    def __init__(self, version, opener):
         """Initialize"""
+        super(WFSCapabilitiesReader, self).__init__(opener)
         self.version = version
         self._infoset = None
 
@@ -398,7 +399,7 @@ class WFSCapabilitiesReader(object):
             The URL to the WFS capabilities document.
         """
         request = self.capabilities_url(url)
-        u = urlopen(request)
+        u = self.opener.open(request)
         return etree.fromstring(u.read())
 
     def readString(self, st):
diff --git a/owslib/util.py b/owslib/util.py
index 9419826..4504ee9 100644
--- a/owslib/util.py
+++ b/owslib/util.py
@@ -11,9 +11,11 @@
 import sys
 from owslib.etree import etree
 import urlparse, urllib2
-from urllib2 import urlopen, HTTPError, Request
-from urllib2 import HTTPPasswordMgrWithDefaultRealm
 from urllib2 import HTTPBasicAuthHandler
+from urllib2 import HTTPCookieProcessor
+from urllib2 import HTTPError
+from urllib2 import HTTPPasswordMgrWithDefaultRealm
+from urllib2 import Request
 from StringIO import StringIO
 
 """
@@ -34,56 +36,108 @@ class ServiceException(Exception):
     #TODO: this should go in ows common module when refactored.  
     pass
 
-def openURL(url_base, data, method='Get', cookies=None, username=None, password=None):
-    ''' function to open urls - wrapper around urllib2.urlopen but with additional checks for OGC service exceptions and url formatting, also handles cookies and simple user password authentication'''
-    url_base.strip() 
-    lastchar = url_base[-1]
-    if lastchar not in ['?', '&']:
-        if url_base.find('?') == -1:
-            url_base = url_base + '?'
+class Client(object):
+    def __init__(self, opener=None, cookies=None, username=None, password=None, url_base=None):
+        if opener is None:
+            self.opener = urllib2.build_opener()
         else:
-            url_base = url_base + '&'
-            
-    if username and password:
-        # Provide login information in order to use the WMS server
-        # Create an OpenerDirector with support for Basic HTTP 
-        # Authentication...
-        passman = HTTPPasswordMgrWithDefaultRealm()
-        passman.add_password(None, url_base, username, password)
-        auth_handler = HTTPBasicAuthHandler(passman)
-        opener = urllib2.build_opener(auth_handler)
-        openit = opener.open
-    else:
-        openit = urlopen
-   
-    try:
-        if method == 'Post':
-            req = Request(url_base, data)
-        else:
-            req=Request(url_base + data)
+            self.opener = opener
+
         if cookies is not None:
-            req.add_header('Cookie', cookies)
-        u = openit(req)
-    except HTTPError, e: #Some servers may set the http header to 400 if returning an OGC service exception or 401 if unauthorised.
-        if e.code in [400, 401]:
-            raise ServiceException, e.read()
-        else:
-            raise e
-    # check for service exceptions without the http header set
-    if u.info()['Content-Type'] in ['text/xml', 'application/xml']:          
-        #just in case 400 headers were not set, going to have to read the xml to see if it's an exception report.
-        #wrap the url stram in a extended StringIO object so it's re-readable
-        u=RereadableURL(u)      
-        se_xml= u.read()
-        se_tree = etree.fromstring(se_xml)
-        serviceException=se_tree.find('{http://www.opengis.net/ows}Exception')
-        if serviceException is None:
-            serviceException=se_tree.find('ServiceException')
-        if serviceException is not None:
-            raise ServiceException, \
-            str(serviceException.text).strip()
-        u.seek(0) #return cursor to start of u      
-    return u
+            self.opener.add_handler(HTTPCookieProcessor(cookies))
+        
+        if None not in (username, password, url_base):
+            passman = HTTPPasswordMgrWithDefaultRealm()
+            passman.add_password(None, url_base, username, password)
+            self.opener.add_handler(HTTPBasicAuthHandler(passman))
+
+
+    def openURL(self, url_base, data, method='Get'):
+        ''' function to open urls - wrapper around urllib2.urlopen but with additional checks for OGC service exceptions and url formatting, also handles cookies and simple user password authentication'''
+        url_base.strip() 
+        lastchar = url_base[-1]
+        if lastchar not in ['?', '&']:
+            if url_base.find('?') == -1:
+                url_base = url_base + '?'
+            else:
+                url_base = url_base + '&'
+      
+        try:
+            if method == 'Post':
+                req = Request(url_base, data)
+            else:
+                req = Request(url_base + data)
+            u = self.opener.open(req)
+        except HTTPError, e: #Some servers may set the http header to 400 if returning an OGC service exception or 401 if unauthorised.
+            if e.code in [400, 401]:
+                raise ServiceException, e.read()
+            else:
+                raise e
+        # check for service exceptions without the http header set
+        if u.info()['Content-Type'] in ['text/xml', 'application/xml']:          
+            #just in case 400 headers were not set, going to have to read the xml to see if it's an exception report.
+            #wrap the url stram in a extended StringIO object so it's re-readable
+            u=RereadableURL(u)      
+            se_xml= u.read()
+            se_tree = etree.fromstring(se_xml)
+            serviceException=se_tree.find('{http://www.opengis.net/ows}Exception')
+            if serviceException is None:
+                serviceException=se_tree.find('ServiceException')
+            if serviceException is not None:
+                raise ServiceException, \
+                str(serviceException.text).strip()
+            u.seek(0) #return cursor to start of u      
+        return u
+
+
+    def http_post(self, url=None, request=None, lang='en-US', timeout=10):
+        """
+
+        Invoke an HTTP POST request 
+
+        Parameters
+        ----------
+
+        - url: the URL of the server
+        - request: the request message
+        - lang: the language
+        - timeout: timeout in seconds
+
+        """
+        assert hasattr(self, 'opener')
+
+        if url is not None:
+            u = urlparse.urlsplit(url)
+            r = urllib2.Request(url, request)
+            r.add_header('User-Agent', 'OWSLib (http://trac.gispython.org/lab/wiki/OwsLib)')
+            r.add_header('Content-type', 'text/xml')
+            r.add_header('Content-length', '%d' % len(request))
+            r.add_header('Accept', 'text/xml')
+            r.add_header('Accept-Language', lang)
+            r.add_header('Accept-Encoding', 'gzip,deflate')
+            r.add_header('Host', u.netloc)
+
+            try:
+                up = self.opener.open(r, timeout=timeout);
+            except TypeError:
+                import socket
+                socket.setdefaulttimeout(timeout)
+                up = self.opener.open(r)
+
+            ui = up.info()  # headers
+            response = up.read()
+            up.close()
+
+            # check if response is gzip compressed
+            if ui.has_key('Content-Encoding'):
+                if ui['Content-Encoding'] == 'gzip':  # uncompress response
+                    import gzip
+                    cds = StringIO(response)
+                    gz = gzip.GzipFile(fileobj=cds)
+                    response = gz.read()
+
+            return response
+
 
 #default namespace for nspath is OWS common
 OWS_NAMESPACE = 'http://www.opengis.net/ows/1.1'
@@ -132,53 +186,6 @@ def testXMLValue(val, attrib=False):
         return None
 
 
-def http_post(url=None, request=None, lang='en-US', timeout=10):
-    """
-
-    Invoke an HTTP POST request 
-
-    Parameters
-    ----------
-
-    - url: the URL of the server
-    - request: the request message
-    - lang: the language
-    - timeout: timeout in seconds
-
-    """
-
-    if url is not None:
-        u = urlparse.urlsplit(url)
-        r = urllib2.Request(url, request)
-        r.add_header('User-Agent', 'OWSLib (http://trac.gispython.org/lab/wiki/OwsLib)')
-        r.add_header('Content-type', 'text/xml')
-        r.add_header('Content-length', '%d' % len(request))
-        r.add_header('Accept', 'text/xml')
-        r.add_header('Accept-Language', lang)
-        r.add_header('Accept-Encoding', 'gzip,deflate')
-        r.add_header('Host', u.netloc)
-
-        try:
-            up = urllib2.urlopen(r,timeout=timeout);
-        except TypeError:
-            import socket
-            socket.setdefaulttimeout(timeout)
-            up = urllib2.urlopen(r)
-
-        ui = up.info()  # headers
-        response = up.read()
-        up.close()
-
-        # check if response is gzip compressed
-        if ui.has_key('Content-Encoding'):
-            if ui['Content-Encoding'] == 'gzip':  # uncompress response
-                import gzip
-                cds = StringIO(response)
-                gz = gzip.GzipFile(fileobj=cds)
-                response = gz.read()
-
-        return response
-
 def xml2string(xml):
     """
 
diff --git a/owslib/wcs.py b/owslib/wcs.py
index fec1dbc..cfb5bbb 100644
--- a/owslib/wcs.py
+++ b/owslib/wcs.py
@@ -18,24 +18,26 @@ import urllib2
 import etree
 from coverage import wcs100, wcs110, wcsBase
 
-def WebCoverageService(url, version=None, xml=None, cookies=None):
+def WebCoverageService(url, version=None, xml=None, opener=None, cookies=None, username=None, password=None):
     ''' wcs factory function, returns a version specific WebCoverageService object '''
     
+    possibly_created_opener = None
     if version is None:
         if xml is None:
-            reader = wcsBase.WCSCapabilitiesReader()
+            reader = wcsBase.WCSCapabilitiesReader(opener=opener, cookies=cookies, username=username, password=password, url_base=url)
+            possibly_created_opener = reader.opener
             request = reader.capabilities_url(url)
-            if cookies is None:
-                xml = urllib2.urlopen(request).read()
-            else:
-                req = urllib2.Request(request)
-                req.add_header('Cookie', cookies)   
-                xml=urllib2.urlopen(req)
+            xml = reader.opener.open(request).read()
         capabilities = etree.etree.fromstring(xml)
         version = capabilities.get('version')
         del capabilities
-        
+
     if version == '1.0.0':
-        return wcs100.WebCoverageService_1_0_0.__new__(wcs100.WebCoverageService_1_0_0, url, xml, cookies)
+        service_class = wcs100.WebCoverageService_1_0_0
     elif version == '1.1.0':
-        return wcs110.WebCoverageService_1_1_0.__new__(wcs110.WebCoverageService_1_1_0,url, xml, cookies)
+        service_class = wcs110.WebCoverageService_1_1_0
+
+    if possibly_created_opener is None:
+        return service_class.__new__(service_class, url, xml, opener, cookies, username, password)
+    else:
+        return service_class.__new__(service_class, url, xml, possibly_created_opener, None, None, None)
diff --git a/owslib/wfs.py b/owslib/wfs.py
index b3c4d94..d16bd33 100644
--- a/owslib/wfs.py
+++ b/owslib/wfs.py
@@ -14,10 +14,12 @@ Web Feature Server (WFS) methods and metadata. Factory function.
 """
 
 from feature import wfs100, wfs200 
-def WebFeatureService(url, version='1.0.0', xml=None):
+def WebFeatureService(url, version='1.0.0', xml=None, opener=None, cookies=None, username=None, password=None):
     ''' wfs factory function, returns a version specific WebFeatureService object '''
     if version in  ['1.0', '1.0.0']:
-        return wfs100.WebFeatureService_1_0_0.__new__(wfs100.WebFeatureService_1_0_0, url, version, xml)
+        return wfs100.WebFeatureService_1_0_0.__new__(wfs100.WebFeatureService_1_0_0,
+                url, version, xml, opener, cookies, username, password)
     elif version in ['2.0', '2.0.0']:
-        return wfs200.WebFeatureService_2_0_0.__new__(wfs200.WebFeatureService_2_0_0, url,  version, xml)
+        return wfs200.WebFeatureService_2_0_0.__new__(wfs200.WebFeatureService_2_0_0,
+                url,  version, xml, opener, cookies, username, password)
 
diff --git a/owslib/wms.py b/owslib/wms.py
index 3d5b0ab..5242116 100644
--- a/owslib/wms.py
+++ b/owslib/wms.py
@@ -18,7 +18,7 @@ Currently supports only version 1.1.1 of the WMS protocol.
 import cgi
 from urllib import urlencode
 from etree import etree
-from .util import openURL
+from .util import Client
 
 
 class ServiceException(Exception):
@@ -41,7 +41,7 @@ class CapabilitiesError(Exception):
     pass
 
 
-class WebMapService(object):
+class WebMapService(Client):
     """Abstraction for OGC Web Map Service (WMS).
 
     Implements IWebMapService.
@@ -56,9 +56,10 @@ class WebMapService(object):
 
     
     def __init__(self, url, version='1.1.1', xml=None, 
-                username=None, password=None
+                username=None, password=None, opener=None, cookies
                 ):
         """Initialize."""
+        super(WebMapService, self).__init__(opener, cookies = cookies, username=username, password=password, url_base = url)
         self.url = url
         self.username = username
         self.password = password
@@ -220,7 +221,7 @@ class WebMapService(object):
         
         data = urlencode(request)
         
-        u = openURL(base_url, data, method, username = self.username, password = self.password)
+        u = self.openURL(base_url, data, method)
 
         # check for service exceptions, and return
         if u.info()['Content-Type'] == 'application/vnd.ogc.se_xml':
@@ -492,28 +493,19 @@ class ContactMetadata:
         else: self.position = None
 
       
-class WMSCapabilitiesReader:
+class WMSCapabilitiesReader(Client):
     """Read and parse capabilities document into a lxml.etree infoset
     """
 
-    def __init__(self, version='1.1.1', url=None, un=None, pw=None):
+    def __init__(self, version='1.1.1', url=None, un=None, pw=None, client=None):
         """Initialize"""
+        super(WMSCapabilitiesReader, self).__init__(client)
         self.version = version
         self._infoset = None
         self.url = url
         self.username = un
         self.password = pw
 
-        #if self.username and self.password:
-            ## Provide login information in order to use the WMS server
-            ## Create an OpenerDirector with support for Basic HTTP 
-            ## Authentication...
-            #passman = HTTPPasswordMgrWithDefaultRealm()
-            #passman.add_password(None, self.url, self.username, self.password)
-            #auth_handler = HTTPBasicAuthHandler(passman)
-            #opener = build_opener(auth_handler)
-            #self._open = opener.open
-
     def capabilities_url(self, service_url):
         """Return a capabilities url
         """
@@ -544,7 +536,7 @@ class WMSCapabilitiesReader:
 
         #now split it up again to use the generic openURL function...
         spliturl=getcaprequest.split('?')
-        u = openURL(spliturl[0], spliturl[1], method='Get', username = self.username, password = self.password)
+        u = self.openURL(spliturl[0], spliturl[1], method='Get')
         return etree.fromstring(u.read())
 
     def readString(self, st):
_______________________________________________
Community mailing list
[email protected]
http://lists.gispython.org/mailman/listinfo/community

Reply via email to