Skip to content

[WebServerPlugin ] Add support for Asynchronous response streaming #289

@ahmedtalhakhan

Description

@ahmedtalhakhan

Describe the bug
WebServerPlugin does not render all data from upstream in a timely manner

To Reproduce
Steps to reproduce the behavior:

  1. Run proxy.py as '--enable-webserver --plugin proxy.plugin.WebServerPlugin'
  2. Some modifications to the WebServerPlugin have been done as explained in the context section
  3. Do a curl to trigger error
  4. See error

Expected behavior
All response data from upstream should come through in a timely fashion. But instead the socket is held for sometime by the proxy/plugin even after reading upstream data and there is a delay in sending the data back to the client

Version information

  • OS: Mac OS Caltalina
  • Browser curl
  • Device: Mac
  • proxy.py Version [e.g. 1.1.1]

Additional context
Following changes have been done to the WebServerPlugin. Note that the route has a * which is supposed to match all.

The upstream server is hosted at local port 5678 and hosts a plain css file.

The modifications are inspired by the plugin reverse_proxy to reach to some upstream server but note that these modifications are required for this plugin to work correctly to process all data. The original code in the reverse_proxy plugin just does a single conn.recv call which is not enough to handle larger datasets.

class WebServerPlugin(HttpWebServerBasePlugin):
    """Demonstrates inbuilt web server routing using plugin."""

    def routes(self) -> List[Tuple[int, str]]:
        return [
            (httpProtocolTypes.HTTP, r'/*'),
        ]

    def handle_request(self, request: HttpParser) -> None:
        upstream = b'http://localhost:5678'

        url = urlparse.urlsplit(upstream)
        assert url.hostname
      
        with socket_connection((text_(url.hostname), url.port if url.port else DEFAULT_HTTP_PORT)) as conn:
               while True:
                    print("I am reading data")
                    data = conn.recv(DEFAULT_BUFFER_SIZE)
                    print(len(data))
                    self.client.queue(memoryview(data))
                    print("pushed data")
                    if not data:
                        print("breaking from loop")
                        break

Now run the following curl command. Note the the css file is just a random css file of size 268K

curl -v http://localhost:8899/css/app.cff42e0c.cs

The output of the curl command is the following. Note that the transfer took upwards of 5 seconds whereas the code has no such delay or sleep. By running the code with the debug prints, it becomes evident that data is read from the upstream socket very quickly, but it is not returned to the client for sometime.

curl -v http://localhost:8899/css/app.cff42e0c.css >o
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8899 (#0)
> GET /css/app.cff42e0c.css HTTP/1.1
> Host: localhost:8899
> User-Agent: curl/7.64.1
> Accept: */*
> 
  0     0    0     0    0     0      0      0 --:--:--  0:00:04 --:--:--     0< HTTP/1.1 200 OK
< X-Powered-By: Express
< Access-Control-Allow-Origin: http://localhost:5678
< Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, PATCH, DELETE
< Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, sessionid
< Accept-Ranges: bytes
< Cache-Control: public, max-age=0
< Last-Modified: Sat, 01 Feb 2020 23:47:30 GMT
< ETag: W/"43093-1700326f6c3"
< Content-Type: text/css; charset=UTF-8
< Content-Length: 274579
< Vary: Accept-Encoding
< Date: Tue, 04 Feb 2020 21:04:27 GMT
< Connection: keep-alive
< 
{ [54576 bytes data]
100  268k  100  268k    0     0  54697      0  0:00:05  0:00:05 --:--:-- 72030
* Connection #0 to host localhost left intact
* Closing connection 0

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions