iwalton3 / plex-mpv-shim

Cast media from Plex Mobile and Web apps to MPV. (Unofficial)
MIT License
368 stars 21 forks source link

BadStatusLine: No status line received - the server has closed the connection #40

Closed SavageCore closed 3 years ago

SavageCore commented 3 years ago

Just upgraded to the latest Plex version (1.23.4.4805) and receive the error "An error occurred trying to play this item."

The following URL returns a 500 status: https://192-168-1-7.2f77b3190b524407988e9734d0f9f466.plex.direct:32400/player/playback/playMedia?type=video&providerIdentifier=com.plexapp.plugins.library&containerKey=%2FplayQueues%2F1897%3Fown%3D1&key=%2Flibrary%2Fmetadata%2F8070&offset=0&machineIdentifier=0e168b635ab27c3e5d588c41474e7c4f058ae0d0&protocol=https&address=195-201-168-83.7975cbb649dd45d9a98afafbd0afcc69.plex.direct&port=32400&token=transient-d121a9c9-c935-4f21-8090-0f1e19b7c9bd&commandID=9&X-Plex-Product=Plex%20Web&X-Plex-Version=4.60.3&X-Plex-Client-Identifier=alv4kxahvnaqhsmfs38xdr59&X-Plex-Platform=Chrome&X-Plex-Platform-Version=91.0&X-Plex-Sync-Version=2&X-Plex-Features=external-media%2Cindirect-media&X-Plex-Model=hosted&X-Plex-Device=Windows&X-Plex-Device-Name=Chrome&X-Plex-Device-Screen-Resolution=2560x1301%2C2560x1440&X-Plex-Token=REDACTED&X-Plex-Language=en-GB&X-Plex-Target-Client-Identifier=dfd88fba-d5ae-4ca5-a5cc-889da0094104

This is the response

<?xml version='1.0' encoding='utf-8'?>
<Response code="2000" status="BadStatusLine: ">
  <Traceback>Traceback (most recent call last):
  File "/usr/local/share/plexmediaserver/Resources/Plug-ins-186bae04e/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/components/runtime.py", line 843, in handle_request
    result = f(**d)
  File "/usr/local/share/plexmediaserver/Resources/Plug-ins-186bae04e/System.bundle/Contents/Code/playerservice.py", line 38, in process_remote_command
    headers=Request.Headers)
  File "/usr/local/share/plexmediaserver/Resources/Plug-ins-186bae04e/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/api/networkkit.py", line 194, in Request
    method=method,
  File "/usr/local/share/plexmediaserver/Resources/Plug-ins-186bae04e/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/api/networkkit.py", line 67, in _http_request
    req = self._core.networking.http_request(url, *args, **kwargs)
  File "/usr/local/share/plexmediaserver/Resources/Plug-ins-186bae04e/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/components/networking.py", line 346, in http_request
    return HTTPRequest(self._core, url, data, h, url_cache, encoding, errors, timeout, immediate, sleep, opener, follow_redirects, method)
  File "/usr/local/share/plexmediaserver/Resources/Plug-ins-186bae04e/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/components/networking.py", line 118, in __init__
    self.load()
  File "/usr/local/share/plexmediaserver/Resources/Plug-ins-186bae04e/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/components/networking.py", line 158, in load
    f = self._opener.open(req, timeout=self._timeout)
  File "/usr/local/share/plexmediaserver/Resources/Python/python27.zip/urllib2.py", line 429, in open
    response = self._open(req, data)
  File "/usr/local/share/plexmediaserver/Resources/Python/python27.zip/urllib2.py", line 447, in _open
    '_open', req)
  File "/usr/local/share/plexmediaserver/Resources/Python/python27.zip/urllib2.py", line 407, in _call_chain
    result = func(*args)
  File "/usr/local/share/plexmediaserver/Resources/Python/python27.zip/urllib2.py", line 1228, in http_open
    return self.do_open(httplib.HTTPConnection, req)
  File "/usr/local/share/plexmediaserver/Resources/Python/python27.zip/urllib2.py", line 1201, in do_open
    r = h.getresponse(buffering=True)
  File "/usr/local/share/plexmediaserver/Resources/Python/python27.zip/httplib.py", line 1148, in getresponse
    response.begin()
  File "/usr/local/share/plexmediaserver/Resources/Python/python27.zip/httplib.py", line 448, in begin
    version, status, reason = self._read_status()
  File "/usr/local/share/plexmediaserver/Resources/Python/python27.zip/httplib.py", line 412, in _read_status
    raise BadStatusLine("No status line received - the server has closed the connection")
BadStatusLine: No status line received - the server has closed the connection
</Traceback>
</Response>
SpencerHastings commented 3 years ago

I found while looking in the logs that before the update, the Plex Media Server would accept a GET request /playQueues/1111?own=1?includeMarkers=1, where the second question mark should be an ampersand but is incorrectly a question mark, and still return a proper response.

After the latest server update, the Plex Media Server has this error show up in the logs Could not convert "own" ("1?includeMarkers=1") to the correct type and returns a 400 error because of that, which ends up causing the issue.

I went in and added in some code to manually fix the one request just to check and the player was able to successfully play media, so I believe if the code to build that url gets fixed then it should fix the issue.

On this line media.py Line 539 the own=1 part of the query is already there with the path since the server includes it, so that is where the ?includeMarkers=1 is being added when it should be &includeMarkers=1.

Hopefully this helps with getting the issue fixed!

iwalton3 commented 3 years ago

Does commenting out that line 539 cause the player to work? It looks like it is already supposed to be adding an "&" and not a "?" unless something is escaped. Do you have the logs from the client of an error?

SpencerHastings commented 3 years ago

The client is getting this error when it does the /playQueues get request I mentioned above:

Exception happened during processing of request from ('192.168.0.21', 56928)
Traceback (most recent call last):
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\socketserver.py", line 650, in process_request_thread
    self.finish_request(request, client_address)
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\socketserver.py", line 360, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\http\server.py", line 647, in __init__
    super().__init__(*args, **kwargs)
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\socketserver.py", line 720, in __init__
    self.handle()
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\http\server.py", line 427, in handle
    self.handle_one_request()
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\http\server.py", line 415, in handle_one_request
    method()
  File "C:\repos\plex-mpv-shim\plex_mpv_shim\client.py", line 198, in do_GET
    self.handle_request("GET")
  File "C:\repos\plex-mpv-shim\plex_mpv_shim\client.py", line 176, in handle_request
    getattr(self, handler)(path, query)
  File "C:\repos\plex-mpv-shim\plex_mpv_shim\client.py", line 318, in playMedia
    media = Media(url, media_type=parsed_media_type, play_queue=playQueue)
  File "C:\repos\plex-mpv-shim\plex_mpv_shim\media.py", line 581, in __init__
    self.upd_play_queue()
  File "C:\repos\plex-mpv-shim\plex_mpv_shim\media.py", line 619, in upd_play_queue
    self.play_queue_xml = XMLCollection(self.get_path(self.play_queue))
  File "C:\repos\plex-mpv-shim\plex_mpv_shim\media.py", line 536, in __init__
    self.tree       = et.parse(urllib.request.urlopen(get_plex_url(url)))
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\urllib\request.py", line 222, in urlopen
    return opener.open(url, data, timeout)
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\urllib\request.py", line 531, in open
    response = meth(req, response)
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\urllib\request.py", line 640, in http_response
    response = self.parent.error(
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\urllib\request.py", line 569, in error
    return self._call_chain(*args)
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\urllib\request.py", line 502, in _call_chain
    result = func(*args)
  File "C:\Users\username\AppData\Local\Programs\Python\Python38\lib\urllib\request.py", line 649, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 400: Bad Request
----------------------------------------

And this is the Plex Server side of that same error where it returns the 400 response, which I believe is cause of the error on the second line of the log here:

Jul 13, 2021 20:04:39.076 [11424] DEBUG - Request: [192.168.0.21:62365 (Subnet)] GET /playQueues/1111?own=1?includeMarkers=1 (6 live) TLS Signed-in Token (username)
Jul 13, 2021 20:04:39.076 [11424] DEBUG - Could not convert "own" ("1?includeMarkers=1") to the correct type
Jul 13, 2021 20:04:39.076 [15004] DEBUG - Completed: [192.168.0.21:62365] 400 GET /playQueues/1111?own=1?includeMarkers=1 (6 live) TLS 0ms 265 bytes

Commenting out that line doesn't work, but I added in some logic from this stackoverflow post to pull out the query being passed in with the path and then have it be added back in during the urlunparse step on the return, and that seems to be working for this one case, I dunno if it would break anything else though that uses this function but I don't think it should. This is what I have the function on line 538 looking like right now:

    def get_path(self, path):

        url_parsed = list(urllib.parse.urlparse(path))
        query = dict(urllib.parse.parse_qsl(url_parsed[4]))
        query.update(dict(urllib.parse.parse_qsl(self.path.query)))

        return urllib.parse.urlunparse((self.path.scheme, self.path.netloc, url_parsed[2],
            self.path.params, urllib.parse.urlencode(query), self.path.fragment))
TheGreenkey commented 3 years ago

I'm getting a similar 400 Bad Request error on my plex instance. plex-mpv-shim crashes at line 538, like @SpencerHastings mentioned. It's the same problem as mentioned before, the path that's passed in already contains a query parameter and self.path.query is then appended with another ?. Fixed it on my system with the following code (adapted from spencer's, with the optimizations from the stackoverflow comments):

    def get_path(self, path):
        parsed_url = urllib.parse.urlparse(path)
        query = urllib.parse.parse_qs(parsed_url.query)
        query.update(urllib.parse.parse_qs(self.path.query))
        query = urllib.parse.urlencode(query, doseq=True)

        return urllib.parse.urlunparse((self.path.scheme, self.path.netloc, parsed_url.path,
            self.path.params, query, self.path.fragment))
SavageCore commented 3 years ago

Confirming @SpencerHastings code works great on Windows.

@TheGreenkey your latest edit removing doseq=True breaks the fix for me.

I will let you guys send a PR? Or want me to do it?

SpencerHastings commented 3 years ago

You can just add the fix if you want unless someone else wants to do a PR.

iwalton3 commented 3 years ago

I'm going to take a look at this and hopefully get a test release posted here today.

TheGreenkey commented 3 years ago

Confirming @SpencerHastings code works great on Windows.

@TheGreenkey your latest edit removing doseq=True breaks the fix for me.

I will let you guys send a PR? Or want me to do it?

You're right, it also breaks on my system without it. I read through the stackoverflow comments again and thought that it was only required on python2, so I removed it without testing again. I changed it back.

iwalton3 commented 3 years ago

Fixed in https://github.com/iwalton3/plex-mpv-shim/releases/tag/v1.10.1. Please let me know if there are further issues.