Plone subrequest and Apache rewrite bad path

Let me say from the start I am not an Apache person so it is entirely possible there is an issue with our config and not with plone.subrequest, but let me lay out exactly what is happening.

Site url: https://portals.mysite.com/demo-portal
Internal server url: http://myserver.com:9500/db01/plone_demo
Apache rewrite rule:
RewriteRule ^/demo-portal(.*|$) http://myserver.com:9500/VirtualHostBase/https/%{HTTP_HOST}:443/db01/plone_demo/VirtualHostRoot/_vh_demo-portal$1 [proxy,last]

This works in almost every case but I noticed a problem with plone.subrequest. When cooking the resource registry, the plone-legacy bundle was not correctly generated because it was not able to find "https://portals.mysite.com/demo-portal/++plone++plone.app.event/event.js" Pulling that resource up directly in the browser is fine. But it did generate this error in the logs:

Traceback (most recent call last):
  File "/sprj/st_zope_plone5/plone-st-03/plone5.2/eggs/Zope-4.8.1-py3.8.egg/ZPublisher/BaseRequest.py", line 518, in traverse
    subobject = self.traverseName(object, entry_name)
  File "/sprj/st_zope_plone5/plone-st-03/plone5.2/eggs/Zope-4.8.1-py3.8.egg/ZPublisher/BaseRequest.py", line 349, in traverseName
    ob2 = adapter.publishTraverse(self, name)
  File "/sprj/st_zope_plone5/plone-st-03/plone5.2/eggs/Zope-4.8.1-py3.8.egg/ZPublisher/BaseRequest.py", line 136, in publishTraverse
    subobject = object[name]
  File "/sprj/st_zope_plone5/plone-st-03/plone5.2/eggs/Zope-4.8.1-py3.8.egg/OFS/ObjectManager.py", line 842, in __getitem__
    raise KeyError(key)
KeyError: 'demo-portal'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/sprj/st_zope_plone5/plone-st-03/plone5.2/eggs/plone.subrequest-1.9.3-py3.8.egg/plone/subrequest/__init__.py", line 153, in subrequest
    traversed = request.traverse(path)
  File "/sprj/st_zope_plone5/plone-st-03/plone5.2/eggs/Zope-4.8.1-py3.8.egg/ZPublisher/BaseRequest.py", line 535, in traverse
    return response.notFoundError(URL)
  File "/sprj/st_zope_plone5/plone-st-03/plone5.2/eggs/Zope-4.8.1-py3.8.egg/ZPublisher/HTTPResponse.py", line 819, in notFoundError
    raise NotFound(self._error_html(
zExceptions.NotFound: <!DOCTYPE html><html>
  <head><title>Site Error</title><meta charset="utf-8" /></head>
  <body bgcolor="white">
  <h2>Site Error</h2>
  <p>An error was encountered while publishing this resource.
  </p>
  <p><strong>Resource not found</strong></p>

  Sorry, the requested resource does not exist.<p>Check the URL and try again.</p><p><b>Resource:</b> https://portals.mysite.com/demo-portal/demo-portal</p>
  <hr noshade="noshade"/>

  <p>Troubleshooting Suggestions</p>

  <ul>
  <li>The URL may be incorrect.</li>
  <li>The parameters passed to this resource may be incorrect.</li>
  <li>A resource that this resource relies on may be
      encountering an error.</li>
  </ul>

  <p>If the error persists please contact the site maintainer.
  Thank you for your patience.
  </p></body></html>

Note that it doubles "demo-portal" for some reason. Debugging at this point in plone.subrequest I see that when passed url=https://portals.mysite.com/demo-portal/++plone++plone.app.event/event.js the value of path is '/VirtualHostBase/https/portals.mysite.com:443/db01/plone_demo/VirtualHostRoot/_vh_demo-portal//demo-portal/++plone++plone.app.event/event.js'.

It's obviously an issue with how the VH is resolved but is the problem with my apache config? bug in plone.subrequest? I have not been able to find any guides on Apache setups for Plone where the site is not hosted on the domain root (we have multiple Plone sites one path deep on this domain), if anyone can give me a reference that would be great.

As a test I added these last three lines to subrequest function

def subrequest(url, root=None, stdout=None, exception_handler=None):
    assert url is not None, 'You must pass a url'
    if six.PY2 and isinstance(url, six.text_type):
        url = url.encode('utf-8')
    _, _, path, query, _ = urlsplit(url)
    parent_request = getRequest()
    assert parent_request is not None, \
        'Unable to get request, perhaps zope.globalrequest is not configured.'
    parent_site = getSite()
    _, _, parent_path, _, _ = urlsplit(parent_site.absolute_url())
    if parent_path and path.startswith(parent_path):
        path = path[len(parent_path):]

So at this point the value of path is changed from /demo-portal/++plone++plone.app.event/event.js to ++plone++plone.app.event/event.js and the final path sent to request.traverse becomes '/VirtualHostBase/https/portals.mysite.com:443/db01/plone_demo/VirtualHostRoot/_vh_demo-portal/++plone++plone.app.event/event.js'.

This appears to work for my case. I do not claim to fully understand what plone.subrequest is doing and the fact that it is so stable gives me pause. But as far as I can tell, that seems to be where my problem lies. Can anyone comment on my approach here, @mauritsvanrees maybe? I'd be happy to put in a proper ticket and PR if I don't sound off base.

edit: I'll also note that this problemseems to be masked in some situations. Consider if my "real" URL matched my Plone id of "plone_demo". Then the path would be /plone_demo/++plone++plone.app.event/event.js and ultimately converted to https://portals.mysite.com/plone_demo/plone_demo/++plone++plone.app.event/event.js. This is wrong but it doesn't matter, Zope will happily let you traverse over the same object as many times as you want.

It is not the first time _vh_ is not handled correctly by the Plone code. I think the Link type has similar issues. This happen when you create the url using portal_url + object path, thus ignoring the _vh_ part. Zope has specific functions to get the object url. From path, you get the object (using traversing) and then call the absolute_url() method on it.
https://docs.plone.org/develop/plone/serving/traversing.html#getting-object-url

and

https://github.com/zopefoundation/Zope/blob/455b32cb800f23f1bd4c8767d8547b53f3aeeb97/src/OFS/Traversable.py#L51
I hope some developer will add some test about it, so it will be easier to catch them.

I did try changing my rewrite rule to the following as I thought perhaps that would fix the double "demo-portal" but it causes every page and resource to 404

RewriteRule ^/(demo-portal.*) 
http://myserver.com:9500/VirtualHostBase/https/%{HTTP_HOST}:443/db01/plone_demo/VirtualHostRoot/_vh_demo-portal/$1
 [proxy,last]

I'm back to trying a monkey patch but I don't like that solution. Seems only a matter of time before the hammer drops.

Maybe related: Site id duplicated in subrequests when we have rewrite rules with _vh_ · Issue #17 · plone/plone.subrequest · GitHub

Plone Foundation Code of Conduct