diff --git a/common/static/coffee/spec/xblock/runtime.v1_spec.coffee b/common/static/coffee/spec/xblock/runtime.v1_spec.coffee
index 266bc7df0eed8e93578332242f01f6f063cef59a..e9c71e9b32fe34d07718eac6cd01051a6f2f043e 100644
--- a/common/static/coffee/spec/xblock/runtime.v1_spec.coffee
+++ b/common/static/coffee/spec/xblock/runtime.v1_spec.coffee
@@ -12,7 +12,7 @@ describe "XBlock.runtime.v1", ->
 
     @runtime = XBlock.runtime.v1(@element, @children)
   it "provides a handler url", ->
-    expect(@runtime.handlerUrl('foo')).toBe('/xblock/fake-usage-id/handler/foo')
+    expect(@runtime.handlerUrl(@element, 'foo')).toBe('/xblock/fake-usage-id/handler/foo')
 
   it "provides a list of children", ->
     expect(@runtime.children).toBe(@children)
diff --git a/common/static/coffee/src/xblock/runtime.v1.coffee b/common/static/coffee/src/xblock/runtime.v1.coffee
index ef05c064722b067c82e0ef4d067dee186fd5acfe..efb9944bd20edac156b5f0f955163de419067900 100644
--- a/common/static/coffee/src/xblock/runtime.v1.coffee
+++ b/common/static/coffee/src/xblock/runtime.v1.coffee
@@ -4,9 +4,21 @@
     childMap[child.name] = child
 
   return {
-    handlerUrl: (handlerName) ->
+    # Generate the handler url for the specified handler.
+    #
+    # element is the html element containing the xblock requesting the url
+    # handlerName is the name of the handler
+    # suffix is the optional url suffix to include in the handler url
+    # query is an optional query-string (note, this should not include a preceding ? or &)
+    handlerUrl: (element, handlerName, suffix, query) ->
       handlerPrefix = $(element).data("handler-prefix")
-      "#{handlerPrefix}/#{handlerName}"
+      suffix = if suffix? then "/#{suffix}" else ''
+      query = if query? then "?#{query}" else ''
+      "#{handlerPrefix}/#{handlerName}#{suffix}#{query}"
+
+    # A list of xblock children of this element
     children: children
+
+    # A map of name -> child for the xblock children of this element
     childMap: childMap
   }
diff --git a/lms/lib/xblock/runtime.py b/lms/lib/xblock/runtime.py
index 4fa48be83a83dd074920406a5ef39f6394abf4f1..cb2c0330251b1d4f0fa2512514ac6f8955064a93 100644
--- a/lms/lib/xblock/runtime.py
+++ b/lms/lib/xblock/runtime.py
@@ -82,12 +82,22 @@ def handler_url(course_id, block, handler, suffix='', query='', thirdparty=False
     if thirdparty:
         view_name = 'xblock_handler_noauth'
 
-    return reverse(view_name, kwargs={
+    url = reverse(view_name, kwargs={
         'course_id': course_id,
         'usage_id': quote_slashes(str(block.scope_ids.usage_id)),
         'handler': handler,
         'suffix': suffix,
-    }) + '?' + query
+    })
+
+    # If suffix is an empty string, remove the trailing '/'
+    if not suffix:
+        url = url.rstrip('/')
+
+    # If there is a query string, append it
+    if query:
+        url += '?' + query
+
+    return url
 
 
 def handler_prefix(course_id, block):
@@ -96,9 +106,13 @@ def handler_prefix(course_id, block):
 
     The prefix is a valid handler url after the handler name is slash-appended
     to it.
-
     """
-    return handler_url(course_id, block, '').rstrip('/')
+    # This depends on handler url having the handler_name as the final piece of the url
+    # so that leaving an empty handler_name really does leave the opportunity to append
+    # the handler_name on the frontend
+
+    # This is relied on by the xblock/runtime.v1.coffee frontend handlerUrl function
+    return handler_url(course_id, block, '').rstrip('/?')
 
 
 class LmsHandlerUrls(object):
diff --git a/lms/lib/xblock/test/test_runtime.py b/lms/lib/xblock/test/test_runtime.py
index c8c4d006f791a296571e48d8976927015fac9fe2..5b3efc171c05d96d97a6347c198a4e31824308fd 100644
--- a/lms/lib/xblock/test/test_runtime.py
+++ b/lms/lib/xblock/test/test_runtime.py
@@ -3,8 +3,10 @@ Tests of the LMS XBlock Runtime and associated utilities
 """
 
 from ddt import ddt, data
+from mock import Mock
 from unittest import TestCase
-from lms.lib.xblock.runtime import quote_slashes, unquote_slashes
+from urlparse import urlparse
+from lms.lib.xblock.runtime import quote_slashes, unquote_slashes, handler_url
 
 TEST_STRINGS = [
     '',
@@ -31,3 +33,46 @@ class TestQuoteSlashes(TestCase):
     @data(*TEST_STRINGS)
     def test_escaped(self, test_string):
         self.assertNotIn('/', quote_slashes(test_string))
+
+
+class TestHandlerUrl(TestCase):
+    """Test the LMS handler_url"""
+
+    def setUp(self):
+        self.block = Mock()
+        self.course_id = "org/course/run"
+
+    def test_trailing_characters(self):
+        self.assertFalse(handler_url(self.course_id, self.block, 'handler').endswith('?'))
+        self.assertFalse(handler_url(self.course_id, self.block, 'handler').endswith('/'))
+
+        self.assertFalse(handler_url(self.course_id, self.block, 'handler', 'suffix').endswith('?'))
+        self.assertFalse(handler_url(self.course_id, self.block, 'handler', 'suffix').endswith('/'))
+
+        self.assertFalse(handler_url(self.course_id, self.block, 'handler', 'suffix', 'query').endswith('?'))
+        self.assertFalse(handler_url(self.course_id, self.block, 'handler', 'suffix', 'query').endswith('/'))
+
+        self.assertFalse(handler_url(self.course_id, self.block, 'handler', query='query').endswith('?'))
+        self.assertFalse(handler_url(self.course_id, self.block, 'handler', query='query').endswith('/'))
+
+    def _parsed_query(self, query_string):
+        """Return the parsed query string from a handler_url generated with the supplied query_string"""
+        return urlparse(handler_url(self.course_id, self.block, 'handler', query=query_string)).query
+
+    def test_query_string(self):
+        self.assertIn('foo=bar', self._parsed_query('foo=bar'))
+        self.assertIn('foo=bar&baz=true', self._parsed_query('foo=bar&baz=true'))
+        self.assertIn('foo&bar&baz', self._parsed_query('foo&bar&baz'))
+
+    def _parsed_path(self, handler_name='handler', suffix=''):
+        """Return the parsed path from a handler_url with the supplied handler_name and suffix"""
+        return urlparse(handler_url(self.course_id, self.block, handler_name, suffix=suffix)).path
+
+    def test_suffix(self):
+        self.assertTrue(self._parsed_path(suffix="foo").endswith('foo'))
+        self.assertTrue(self._parsed_path(suffix="foo/bar").endswith('foo/bar'))
+        self.assertTrue(self._parsed_path(suffix="/foo/bar").endswith('/foo/bar'))
+
+    def test_handler_name(self):
+        self.assertIn('handler1', self._parsed_path('handler1'))
+        self.assertIn('handler_a', self._parsed_path('handler_a'))