Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reading empty body src hangs server #40

Open
benmerckx opened this issue Jul 1, 2016 · 12 comments
Open

Reading empty body src hangs server #40

benmerckx opened this issue Jul 1, 2016 · 12 comments
Assignees
Milestone

Comments

@benmerckx
Copy link
Member

benmerckx commented Jul 1, 2016

The tests are coming along but I'm having trouble reading the body properly if it's empty. Any server based on tcpcontainer fails here, without any Success or Failure but simply hangs indefinitely. I solved this previously in monsoon in a very hacky way. But I think this should be solved properly :)

@benmerckx
Copy link
Member Author

benmerckx commented Jul 3, 2016

So I realize the above report wasn't that helpful. Here's an example:

import tink.http.containers.TcpContainer;
import tink.http.Response;
using tink.CoreApi;

class Main {

    public static function main() {
        var container: TcpContainer = new TcpContainer(8000);
        container.run(function(req) {
            return switch (req.body) {
                case Plain(src):
                    return src.all().map(function (body)
                        return switch body {
                            case Success(bytes):
                                ('Plain body: '+bytes.toString(): OutgoingResponse);
                            case Failure(e):
                                ('Failed to parse body': OutgoingResponse);
                        }
                    );
                default:
                    Future.sync(('Parsed body': OutgoingResponse));
            }
        });
    }

}

This works when sending a request with a body attached (response is Plain body: ..body..). But a simple GET request without a body fails (no response, browser timeouts).

@back2dos
Copy link
Member

back2dos commented Jul 4, 2016

Alright, thanks, I'll look into this.

@back2dos back2dos self-assigned this Jul 4, 2016
@back2dos back2dos added this to the 1.0.0 milestone Jul 8, 2016
@benmerckx benmerckx mentioned this issue Oct 15, 2016
@benmerckx
Copy link
Member Author

benmerckx commented Oct 16, 2016

I followed this path (we have a Source which has no bytes left so should result in EOF).

https://github.com/haxetink/tink_io/blob/master/src/tink/io/Buffer.hx#L214

var transfered = source.readBytes(bytes, end, toRead);

Input.readBytes doesn't throw EOF, but simply results in 0 bytes read.
This causes suspend to be called on StdSource:

https://github.com/haxetink/tink_io/blob/master/src/tink/io/Pipe.hx#L71

  function read()
    source.read(buffer).handle(function (o) switch o {
      case Success(_.isEof => true):
        source.close();
        buffer.seal();
        flush();
      case Success(v):
        if (v == 0 && buffer.available == 0)
          suspend(); // Here
        else
          flush();
      case Failure(e):
        terminate(SourceFailed(e));
    });

That ends up in a new read call because there's nothing in suspended:

https://github.com/haxetink/tink_io/blob/master/src/tink/io/Pipe.hx#L47

  function suspend() {
    if (this.bufferWidth > 0) 
      switch suspended.pop() {
        case null: read(); // Here
        case next:
          releaseBuffer();
          suspended.add(this);
          next.resume();
      }
    else read();
  }

So essentially this causes an infinite loop between read and suspend.

I'm unsure what suspended is meant for, so I don't really know how to fix this.

@back2dos
Copy link
Member

Well, suspend puts a pending pipe into "standby" if you will. So that if you have many pipes the ones that have no data passing through, they don't compete with those that are busy.

Does the problem disappear if you always just read in suspend?

@benmerckx
Copy link
Member Author

That puts me in the same position. Because read always ends up calling supend after if (v == 0 && buffer.available == 0) so we're looping again.

What fixes things for me is adding else return Progress.EOF; after this condition:
https://github.com/haxetink/tink_io/blob/master/src/tink/io/Buffer.hx#L219
But I know (think) that's not how it's supposed to work :)

@back2dos
Copy link
Member

Ok, I have checked this, which was the most intelligible spec I could find: http://httpwg.org/specs/rfc7230.html#message.body

My understanding is that if there's neither Content-Length nor Transfer-Encoding, there's simply no body. And if the client doesn't close its end of the socket, we're out of luck. So in those cases I guess we could just use an empty source. Can you confirm?

@back2dos
Copy link
Member

On second thought if no such thing is specified for POST, PUT or PATCH we might still try reading for "a while" just so that non-conforming clients get through. Or maybe all this should be a middleware anyway ...

@benmerckx
Copy link
Member Author

benmerckx commented Oct 16, 2016

I had to look up a lot of things :)
Seems you're right about content-length and transfer-encoding. In any case I think some timeouts should be set in place somewhere to prevent these loops in other scenarios.

What I wonder though is, when I tried running it through a streamparser I got -1. Does that signify the closing of the client's end or just that there's nothing to read at that time (but there could be data later on)?

@benmerckx
Copy link
Member Author

After reading this I think I have most of it wrong :)

Is the problem the server waiting for a body, while the client is already waiting for a response? I've seen the curlclient succeed so I wonder why it's different there. I also still don't get when EOF can be expected, is that only used for closed connections?

@back2dos
Copy link
Member

back2dos commented Oct 17, 2016

If curl shuts down its end of the socket then that would cause an EOF on the server. There are some chances that the TcpClient will work for the same reason.

Other clients (e.g. NodeClient) will leave the connection open for reuse. In that case the server receives no more data on the inbound stream but neither does it get an EOF. So yeah, the server must basically know in that case that no data is to be expected.

If you want to move forward with the tests, I suggest not to read the body for GET for now. Dealing with this properly entails implementing chunked encoding and using it when no content-length is specified (unless the underlying implementation takes care of it anyway).

@benmerckx
Copy link
Member Author

I got it now, thanks! :)

@benmerckx
Copy link
Member Author

benmerckx commented Dec 13, 2016

I'm seeing what I think is the same problem the other way around happening when using a TcpClient to read a body from a server which does not specify a content-length (and I assume does not close the connection).

Edit: never mind, seems like a different issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants