URLs: changed paramter delimiter from / to ? and & as it is usual for URLs, update docs
diff --git a/CHANGES.rst b/CHANGES.rst
index 7eb2a1c..ffc6e26 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -507,22 +507,24 @@
 --------------------------
 
 - Starting from this release, only 2.7 and 3.4 (or newer) are supported. The
-  sourcecode is compatible to the 2.x and 3.x series without any changes. The
+  source code is compatible to the 2.x and 3.x series without any changes. The
   support for earlier Python versions than 2.7 is removed, please refer to the
   pyserial-legacy (V2.x) series if older Python versions are a requirement).
 - remove file ``FileLike`` class, add ``read_until`` and ``iread_until`` to
   ``SerialBase``
 - remove set* functions, please use the properties instead
-- RS485 support changed (rts_toggle removed, added serial.rs485 module and
-  rs485_mode property)
-- socket:// and rfc2217:// handlers use the IPv6 compatible socket.create_connection
+- RS485 support changed (rts_toggle removed, added ``serial.rs485`` module and
+  ``rs485_mode`` property)
+- ``socket://`` and ``rfc2217://`` handlers use the IPv6 compatible ``socket.create_connection``
 - remove obsolete examples
 - finish update to BSD license
 - update links to point to github
 - [Patch pyserial:34] Improvements to port_publisher.py example
 - [Feature pyserial:39] Support BlueTooth serial port discovery on Linux
-- Use setuptools if aviliable, fall back to distutils if unaviliable.
+- Use setuptools if available, fall back to distutils if unavailable.
 - miniterm: changed command line options, translations, support encodings
+- URL handlers now require the proper format (``?`` and ``&``) for arguments
+  instead of ``/`` (e.g. ``rfc2217://localhost:7000?ign_set_control&timeout=5.5``)
 
 Bugfixes:
 
diff --git a/documentation/pyserial_api.rst b/documentation/pyserial_api.rst
index 771b673..c32cece 100644
--- a/documentation/pyserial_api.rst
+++ b/documentation/pyserial_api.rst
@@ -800,6 +800,17 @@
 
     .. versionadded:: 2.5
 
+.. function:: iterbytes(b)
+
+    :param b: bytes, bytearray or memoryview
+    :returns: a generator that yields bytes
+
+    Some versions of Python (3.x) would return integers instead of bytes when
+    looping over an instance of ``bytes``.  This helper function ensures that
+    bytes are returned.
+
+    .. versionadded:: 3.0
+
 
 .. _URLs:
 
@@ -807,11 +818,13 @@
 ----
 The function :func:`serial_for_url` accepts the following types of URLs:
 
-- ``rfc2217://<host>:<port>[/<option>[/<option>]]``
-- ``socket://<host>:<port>[/<option>[/<option>]]``
-- ``loop://[<option>[/<option>]]``
+- ``rfc2217://<host>:<port>[?<option>[&<option>...]]``
+- ``socket://<host>:<port>[?logging={debug|info|warning|error}]``
+- ``loop://[?logging={debug|info|warning|error}]``
 - ``spy://port[?option[=value][&option[=value]]]``
 
+.. versionchanged:: 3.0 Options are specified with ``?`` and ``&`` instead of ``/``
+
 Device names are also supported, e.g.:
 
 - ``/dev/ttyUSB0`` (Linux)
@@ -842,7 +855,7 @@
       timeout applies to the initial Telnet / :rfc:`2271` negotiation as well
       as changing port settings or control line change commands.
 
-    - ``logging=[debug|info|warning|error]``: Prints diagnostic messages (not
+    - ``logging={debug|info|warning|error}``: Prints diagnostic messages (not
       useful for end users). It uses the logging module and a logger called
       ``pySerial.rfc2217`` so that the application can setup up logging
       handlers etc. It will call :meth:`logging.basicConfig` which initializes
@@ -857,7 +870,7 @@
 
     Supported options in the URL are:
 
-    - ``logging=[debug|info|warning|error]``: Prints diagnostic messages (not
+    - ``logging={debug|info|warning|error}``: Prints diagnostic messages (not
       useful for end users). It uses the logging module and a logger called
       ``pySerial.socket`` so that the application can setup up logging handlers
       etc. It will call :meth:`logging.basicConfig` which initializes for
@@ -870,7 +883,7 @@
 
     Supported options in the URL are:
 
-    - ``logging=[debug|info|warning|error]``: Prints diagnostic messages (not
+    - ``logging={debug|info|warning|error}``: Prints diagnostic messages (not
       useful for end users). It uses the logging module and a logger called
       ``pySerial.loop`` so that the application can setup up logging handlers
       etc. It will call :meth:`logging.basicConfig` which initializes for
@@ -958,16 +971,19 @@
         000002.284 RX   00F0  F0 F1 F2 F3 F4 F5 F6 F7  F8 F9 FA FB FC FD FE FF  ........ ........
         000002.284 BRK  sendBreak 0.25
 
+    .. versionadded:: 3.0
+
 
 
 Examples:
 
 - ``rfc2217://localhost:7000``
-- ``rfc2217://localhost:7000/poll_modem``
-- ``rfc2217://localhost:7000/ign_set_control/timeout=5.5``
+- ``rfc2217://localhost:7000?poll_modem``
+- ``rfc2217://localhost:7000?ign_set_control&timeout=5.5``
 - ``socket://localhost:7777``
-- ``loop://logging=debug``
+- ``loop://?logging=debug``
 - ``hwgrep://0451:f432`` (USB VID:PID)
+- ``spy://COM54?dev=log.txt``
 
 Tools
 =====
diff --git a/serial/rfc2217.py b/serial/rfc2217.py
index 9c5d68b..c87b14b 100644
--- a/serial/rfc2217.py
+++ b/serial/rfc2217.py
@@ -535,35 +535,29 @@
     def fromURL(self, url):
         """extract host and port from an URL string"""
         parts = urlparse.urlsplit(url)
-        if parts.scheme.lower() != "rfc2217":
-            raise SerialException('expected a string in the form "rfc2217://<host>:<port>[/option[/option...]]": not starting with rfc2217:// (%r)' % (parts.scheme,))
+        if parts.scheme != "rfc2217":
+            raise SerialException('expected a string in the form "rfc2217://<host>:<port>[?option[&option...]]": not starting with rfc2217:// (%r)' % (parts.scheme,))
         try:
             # process options now, directly altering self
-            for option in parts.path.lower().split('/'):
-                if '=' in option:
-                    option, value = option.split('=', 1)
-                else:
-                    value = None
-                if not option:
-                    pass
-                elif option == 'logging':
+            for option, values in urlparse.parse_qs(parts.query, True).items():
+                if option == 'logging':
                     logging.basicConfig()   # XXX is that good to call it here?
                     self.logger = logging.getLogger('pySerial.rfc2217')
-                    self.logger.setLevel(LOGGER_LEVELS[value])
+                    self.logger.setLevel(LOGGER_LEVELS[values[0]])
                     self.logger.debug('enabled logging')
                 elif option == 'ign_set_control':
                     self._ignore_set_control_answer = True
                 elif option == 'poll_modem':
                     self._poll_modem_state = True
                 elif option == 'timeout':
-                    self._network_timeout = float(value)
+                    self._network_timeout = float(values[0])
                 else:
                     raise ValueError('unknown option: %r' % (option,))
             # get host and port
             host, port = parts.hostname, parts.port
             if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
         except ValueError as e:
-            raise SerialException('expected a string in the form "rfc2217://<host>:<port>[/option[/option...]]": %s' % e)
+            raise SerialException('expected a string in the form "rfc2217://<host>:<port>[?option[&option...]]": %s' % e)
         return (host, port)
 
     #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
diff --git a/serial/urlhandler/protocol_loop.py b/serial/urlhandler/protocol_loop.py
index 02167e5..10dedeb 100644
--- a/serial/urlhandler/protocol_loop.py
+++ b/serial/urlhandler/protocol_loop.py
@@ -102,26 +102,20 @@
     def fromURL(self, url):
         """extract host and port from an URL string"""
         parts = urlparse.urlsplit(url)
-        if parts.scheme.lower() != "loop":
-            raise SerialException('expected a string in the form "loop://[option[/option...]]": not starting with loop:// (%r)' % (parts.scheme,))
+        if parts.scheme != "loop":
+            raise SerialException('expected a string in the form "loop://[?logging={debug|info|warning|error}]": not starting with loop:// (%r)' % (parts.scheme,))
         try:
             # process options now, directly altering self
-            for option in parts.path.split('/'):
-                if '=' in option:
-                    option, value = option.split('=', 1)
-                else:
-                    value = None
-                if not option:
-                    pass
-                elif option == 'logging':
+            for option, values in urlparse.parse_qs(parts.query, True).items():
+                if option == 'logging':
                     logging.basicConfig()   # XXX is that good to call it here?
                     self.logger = logging.getLogger('pySerial.loop')
-                    self.logger.setLevel(LOGGER_LEVELS[value])
+                    self.logger.setLevel(LOGGER_LEVELS[values[0]])
                     self.logger.debug('enabled logging')
                 else:
                     raise ValueError('unknown option: %r' % (option,))
         except ValueError as e:
-            raise SerialException('expected a string in the form "[loop://][option[/option...]]": %s' % e)
+            raise SerialException('expected a string in the form "loop://[?logging={debug|info|warning|error}]": %s' % e)
 
     #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
 
diff --git a/serial/urlhandler/protocol_socket.py b/serial/urlhandler/protocol_socket.py
index 4538e17..3dedb48 100644
--- a/serial/urlhandler/protocol_socket.py
+++ b/serial/urlhandler/protocol_socket.py
@@ -104,21 +104,15 @@
     def fromURL(self, url):
         """extract host and port from an URL string"""
         parts = urlparse.urlsplit(url)
-        if parts.scheme.lower() != "socket":
-            raise SerialException('expected a string in the form "socket://<host>:<port>[/option[/option...]]": not starting with socket:// (%r)' % (parts.scheme,))
+        if parts.scheme != "socket":
+            raise SerialException('expected a string in the form "socket://<host>:<port>[?logging={debug|info|warning|error}]": not starting with socket:// (%r)' % (parts.scheme,))
         try:
             # process options now, directly altering self
-            for option in parts.path.lower().split('/'):
-                if '=' in option:
-                    option, value = option.split('=', 1)
-                else:
-                    value = None
-                if not option:
-                    pass
-                elif option == 'logging':
+            for option, values in urlparse.parse_qs(parts.query, True).items():
+                if option == 'logging':
                     logging.basicConfig()   # XXX is that good to call it here?
                     self.logger = logging.getLogger('pySerial.socket')
-                    self.logger.setLevel(LOGGER_LEVELS[value])
+                    self.logger.setLevel(LOGGER_LEVELS[values[0]])
                     self.logger.debug('enabled logging')
                 else:
                     raise ValueError('unknown option: %r' % (option,))
@@ -126,7 +120,7 @@
             host, port = parts.hostname, parts.port
             if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
         except ValueError as e:
-            raise SerialException('expected a string in the form "socket://<host>:<port>[/option[/option...]]": %s' % e)
+            raise SerialException('expected a string in the form "socket://<host>:<port>[?logging={debug|info|warning|error}]": %s' % e)
         return (host, port)
 
     #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
diff --git a/serial/urlhandler/protocol_spy.py b/serial/urlhandler/protocol_spy.py
index a4deaef..d6ed458 100644
--- a/serial/urlhandler/protocol_spy.py
+++ b/serial/urlhandler/protocol_spy.py
@@ -165,13 +165,18 @@
         formatter = FormatHexdump
         color = False
         output = sys.stderr
-        for option, values in urlparse.parse_qs(parts.query, True).items():
-            if option == 'dev':
-                output = open(values[0], 'w')
-            elif option == 'color':
-                color = True
-            elif option == 'raw':
-                formatter = FormatRaw
+        try:
+            for option, values in urlparse.parse_qs(parts.query, True).items():
+                if option == 'dev':
+                    output = open(values[0], 'w')
+                elif option == 'color':
+                    color = True
+                elif option == 'raw':
+                    formatter = FormatRaw
+                else:
+                    raise ValueError('unknown option: %r' % (option,))
+        except ValueError as e:
+            raise SerialException('expected a string in the form "spy://port[?option[=value][&option[=value]]]": %s' % e)
         self.formatter = formatter(output, color)
         return ''.join([parts.netloc, parts.path])